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,522 @@
1
+ """Apply phase orchestration: apply, present, confirm, finalize."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from typing import Any
7
+
8
+ from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
9
+ from structlog import get_logger
10
+
11
+ from roadmap.core.services.sync.error_classification import ErrorClassifier
12
+
13
+ logger = get_logger(__name__)
14
+
15
+
16
+ def display_error_summary(
17
+ errors: dict[str, str], console_inst: Any, verbose: bool = False
18
+ ) -> None:
19
+ """Display a user-friendly error summary with fix suggestions using ErrorClassifier.
20
+
21
+ Args:
22
+ errors: Dict of issue_id -> error message
23
+ console_inst: Rich console instance
24
+ verbose: If True, show detailed error info including affected issue IDs
25
+ """
26
+ if not errors:
27
+ return
28
+
29
+ # Use ErrorClassifier to categorize errors
30
+ classifier = ErrorClassifier()
31
+ classifier.add_errors(errors)
32
+ summary = classifier.get_summary_dict()
33
+
34
+ if not summary or sum(summary.values()) == 0:
35
+ return
36
+
37
+ console_inst.print("\n[bold yellow]āš ļø Sync Errors[/bold yellow]")
38
+ console_inst.print(
39
+ f"[dim]Total errors: {sum(summary.values())} across {len(errors)} issues[/dim]\n"
40
+ )
41
+
42
+ # Map categories to display names, icons, and colors
43
+ category_display = {
44
+ "dependency_errors": ("šŸ”— Dependency Errors", "yellow"),
45
+ "api_errors": ("🌐 API Errors", "yellow"),
46
+ "auth_errors": ("šŸ”’ Authentication Errors", "red"),
47
+ "data_errors": ("šŸ’¾ Data Errors", "yellow"),
48
+ "resource_errors": ("šŸ“¦ Resource Errors", "yellow"),
49
+ "file_system_errors": ("šŸ“ File System Errors", "yellow"),
50
+ "unknown_errors": ("ā“ Unknown Errors", "yellow"),
51
+ }
52
+
53
+ for category, count in summary.items():
54
+ if count == 0:
55
+ continue
56
+
57
+ display_name, color = category_display.get(
58
+ category, (category.replace("_", " ").title(), "yellow")
59
+ )
60
+ console_inst.print(f"[{color}] • {display_name} ({count})[/{color}]")
61
+
62
+ # Get recommendation for this category
63
+ recommendation = classifier.get_recommendation(category)
64
+ if recommendation:
65
+ console_inst.print(f"[dim] Fix: {recommendation}[/dim]")
66
+
67
+ # In verbose mode, show affected issue IDs and their error messages
68
+ if verbose:
69
+ affected_issues = classifier.get_issues_by_category(category)
70
+ if affected_issues:
71
+ console_inst.print("[dim] Affected issues:[/dim]")
72
+ for issue_id in affected_issues[:5]: # Show first 5 examples
73
+ error_msg = errors.get(issue_id, "Unknown error")
74
+ # Truncate long error messages
75
+ if len(error_msg) > 80:
76
+ error_msg = error_msg[:77] + "..."
77
+ console_inst.print(
78
+ f"[dim] - {issue_id[:8]}: {error_msg}[/dim]"
79
+ )
80
+ if len(affected_issues) > 5:
81
+ console_inst.print(
82
+ f"[dim] ... and {len(affected_issues) - 5} more[/dim]"
83
+ )
84
+ console_inst.print() # Add blank line between categories
85
+
86
+
87
+ def perform_apply_phase(
88
+ core: Any,
89
+ orchestrator: Any,
90
+ console_inst: Any,
91
+ analysis_report: Any,
92
+ force_local: bool,
93
+ force_remote: bool,
94
+ push: bool,
95
+ pull: bool,
96
+ verbose: bool,
97
+ ) -> Any:
98
+ """Run the actual apply phase: perform sync and display summary."""
99
+ # Create progress spinner for sync operation
100
+ with Progress(
101
+ SpinnerColumn(),
102
+ TextColumn("[bold blue]{task.description}"),
103
+ TimeElapsedColumn(),
104
+ console=console_inst,
105
+ ) as progress:
106
+ progress.add_task("Syncing with remote...", total=None)
107
+
108
+ report = orchestrator.sync_all_issues(
109
+ dry_run=False,
110
+ force_local=force_local,
111
+ force_remote=force_remote,
112
+ push_only=push,
113
+ pull_only=pull,
114
+ )
115
+
116
+ if report.error:
117
+ console_inst.print(f"\nāŒ Sync error: {report.error}", style="bold red")
118
+ sys.exit(1)
119
+
120
+ # Display error summary BEFORE sync results
121
+ if report.errors:
122
+ display_error_summary(report.errors, console_inst, verbose)
123
+ console_inst.print()
124
+
125
+ console_inst.print("[bold cyan]āœ… Sync Results[/bold cyan]")
126
+
127
+ pushed = report.issues_pushed
128
+ pulled = report.issues_pulled
129
+
130
+ if pushed > 0:
131
+ console_inst.print(f" šŸ“¤ Pushed: {pushed}")
132
+ if pulled > 0:
133
+ console_inst.print(f" šŸ“„ Pulled: {pulled}")
134
+
135
+ if pushed == 0 and pulled == 0:
136
+ console_inst.print(" āœ“ Everything up-to-date")
137
+
138
+ console_inst.print()
139
+
140
+ if analysis_report.issues_needs_pull > 0 or analysis_report.issues_needs_push > 0:
141
+ console_inst.print(
142
+ "[dim]šŸ’” Tip: The baseline is the 'agreed-upon state' from the last sync.[/dim]"
143
+ )
144
+ console_inst.print(
145
+ "[dim] After this sync completes, the baseline updates. The next sync should[/dim]"
146
+ )
147
+ console_inst.print(
148
+ "[dim] show these same issues as 'up-to-date' (all three states match).[/dim]"
149
+ )
150
+
151
+ return report
152
+
153
+
154
+ def present_apply_intent(analysis_report: Any, console_inst: Any) -> bool:
155
+ """Present whether there are changes to apply and return True if apply is needed."""
156
+ if (
157
+ analysis_report.issues_needs_push > 0
158
+ or analysis_report.issues_needs_pull > 0
159
+ or analysis_report.conflicts_detected > 0
160
+ ):
161
+ console_inst.print("\n✨ [bold cyan]Applied Changes[/bold cyan]")
162
+ return True
163
+ else:
164
+ console_inst.print(
165
+ "\n[bold green]āœ“ Already up-to-date, no changes needed[/bold green]"
166
+ )
167
+ return False
168
+
169
+
170
+ def confirm_and_apply(
171
+ core: Any,
172
+ orchestrator: Any,
173
+ console_inst: Any,
174
+ analysis_report: Any,
175
+ force_local: bool,
176
+ force_remote: bool,
177
+ push: bool,
178
+ pull: bool,
179
+ verbose: bool,
180
+ interactive: bool = False,
181
+ ) -> Any | None:
182
+ """Run the apply phase with optional interactive conflict resolution."""
183
+ # If interactive mode and there are conflicts, resolve them first
184
+ if interactive and analysis_report.conflicts_detected > 0:
185
+ from roadmap.adapters.cli.sync_handlers.interactive_resolver import (
186
+ InteractiveConflictResolver,
187
+ )
188
+
189
+ console_inst.print(
190
+ f"\n[bold yellow]āš ļø {analysis_report.conflicts_detected} conflicts detected[/bold yellow]"
191
+ )
192
+ console_inst.print("[dim]Entering interactive resolution mode...[/dim]\n")
193
+
194
+ # Collect conflicts from analysis report
195
+ conflicts = []
196
+ issues_by_id = {}
197
+
198
+ for change in analysis_report.changes:
199
+ if change.has_conflict:
200
+ # Build issues_by_id mapping
201
+ if change.local_state:
202
+ issues_by_id[change.issue_id] = change.local_state
203
+
204
+ # Create conflict object (simplified for now)
205
+ # In a real implementation, you'd extract the actual Conflict objects
206
+ # from the orchestrator or conflict resolver
207
+ console_inst.print(
208
+ f"[dim]Conflict on issue: {change.issue_id} - {change.title}[/dim]"
209
+ )
210
+
211
+ # If we have conflicts to resolve interactively
212
+ if conflicts:
213
+ resolver = InteractiveConflictResolver(console_inst)
214
+ resolutions = resolver.resolve_interactively(conflicts, issues_by_id)
215
+
216
+ # Apply resolutions
217
+ # This would integrate with the orchestrator's conflict resolver
218
+ # to apply the chosen resolutions
219
+ resolver.show_conflict_summary(conflicts, resolutions)
220
+
221
+ report = perform_apply_phase(
222
+ core,
223
+ orchestrator,
224
+ console_inst,
225
+ analysis_report,
226
+ force_local,
227
+ force_remote,
228
+ push,
229
+ pull,
230
+ verbose,
231
+ )
232
+
233
+ return report
234
+
235
+
236
+ def _display_sync_metrics(console_inst: Any, metrics: dict[str, Any]) -> None:
237
+ """Display formatted sync metrics summary.
238
+
239
+ Args:
240
+ console_inst: Rich console instance
241
+ metrics: Dictionary of metrics from SyncMetrics.to_dict()
242
+ """
243
+ from rich.table import Table
244
+
245
+ console_inst.print("\n[bold cyan]šŸ“Š Sync Metrics[/bold cyan]")
246
+
247
+ # Create main metrics table
248
+ table = Table(show_header=True, header_style="bold")
249
+ table.add_column("Metric", style="cyan")
250
+ table.add_column("Value", style="green")
251
+
252
+ # Extract and format key metrics
253
+ def format_time(seconds: float | int) -> str:
254
+ """Format seconds into readable time string."""
255
+ if isinstance(seconds, (int, float)) and seconds > 0:
256
+ if seconds < 1:
257
+ return f"{seconds * 1000:.0f}ms"
258
+ return f"{seconds:.2f}s"
259
+ return "N/A"
260
+
261
+ def format_count(count: int | None) -> str:
262
+ """Format count or return N/A."""
263
+ return str(count) if count is not None else "N/A"
264
+
265
+ # Deduplication metrics
266
+ if metrics.get("duplicates_detected"):
267
+ table.add_row(
268
+ "Duplicates Detected",
269
+ format_count(metrics.get("duplicates_detected")),
270
+ )
271
+ if metrics.get("duplicates_resolved"):
272
+ table.add_row(
273
+ "Duplicates Resolved",
274
+ format_count(metrics.get("duplicates_resolved")),
275
+ )
276
+
277
+ # Local dedup metrics
278
+ if metrics.get("local_dedup_count"):
279
+ reduction_pct = (
280
+ (
281
+ metrics.get("local_dedup_count", 0)
282
+ / (
283
+ metrics.get("local_dedup_count", 0)
284
+ + metrics.get("local_final_count", 1)
285
+ )
286
+ )
287
+ * 100
288
+ if (
289
+ metrics.get("local_dedup_count", 0)
290
+ + metrics.get("local_final_count", 1)
291
+ )
292
+ > 0
293
+ else 0
294
+ )
295
+ table.add_row(
296
+ "Local Dedup Reduction",
297
+ f"{metrics.get('local_dedup_count')} duplicates "
298
+ f"({reduction_pct:.1f}% reduction)",
299
+ )
300
+
301
+ # Remote dedup metrics
302
+ if metrics.get("remote_dedup_count"):
303
+ reduction_pct = (
304
+ (
305
+ metrics.get("remote_dedup_count", 0)
306
+ / (
307
+ metrics.get("remote_dedup_count", 0)
308
+ + metrics.get("remote_final_count", 1)
309
+ )
310
+ )
311
+ * 100
312
+ if (
313
+ metrics.get("remote_dedup_count", 0)
314
+ + metrics.get("remote_final_count", 1)
315
+ )
316
+ > 0
317
+ else 0
318
+ )
319
+ table.add_row(
320
+ "Remote Dedup Reduction",
321
+ f"{metrics.get('remote_dedup_count')} duplicates "
322
+ f"({reduction_pct:.1f}% reduction)",
323
+ )
324
+
325
+ # Timing metrics
326
+ if metrics.get("total_duration_seconds"):
327
+ table.add_row(
328
+ "Total Duration",
329
+ format_time(metrics.get("total_duration_seconds", 0)),
330
+ )
331
+
332
+ # Fetch metrics
333
+ if metrics.get("total_fetch_time_seconds"):
334
+ table.add_row(
335
+ "Fetch Time",
336
+ format_time(metrics.get("total_fetch_time_seconds", 0)),
337
+ )
338
+
339
+ # Conflict metrics
340
+ if metrics.get("merge_conflicts_found"):
341
+ table.add_row(
342
+ "Merge Conflicts",
343
+ format_count(metrics.get("merge_conflicts_found")),
344
+ )
345
+
346
+ # API call metrics
347
+ if metrics.get("api_calls_made"):
348
+ table.add_row(
349
+ "API Calls",
350
+ format_count(metrics.get("api_calls_made")),
351
+ )
352
+
353
+ if metrics.get("api_rate_limit_remaining") is not None:
354
+ table.add_row(
355
+ "API Rate Limit Remaining",
356
+ format_count(metrics.get("api_rate_limit_remaining")),
357
+ )
358
+
359
+ console_inst.print(table)
360
+
361
+
362
+ def finalize_sync(
363
+ core: Any,
364
+ console_inst: Any,
365
+ report: Any,
366
+ pre_sync_issue_count: int,
367
+ verbose: bool,
368
+ backend_type: str | None = None,
369
+ ) -> None:
370
+ """Finalize sync run: capture post-sync baseline and print completion messages."""
371
+ from roadmap.adapters.cli.sync_handlers.baseline_ops import (
372
+ capture_and_save_post_sync_baseline,
373
+ )
374
+
375
+ console_inst.print("[bold]BASELINE CHANGES:[/bold]")
376
+ console_inst.print(f" Before: {pre_sync_issue_count} issues in baseline")
377
+
378
+ baseline_updated = capture_and_save_post_sync_baseline(
379
+ core, console_inst, pre_sync_issue_count, verbose
380
+ )
381
+ if hasattr(report, "baseline_update_failed"):
382
+ report.baseline_update_failed = not baseline_updated
383
+
384
+ _run_post_sync_repairs(core, console_inst, report, backend_type)
385
+ _save_and_display_sync_metrics(core, console_inst, report)
386
+
387
+ console_inst.print()
388
+ console_inst.print("āœ… Sync completed successfully", style="bold green")
389
+
390
+
391
+ def _should_run_post_sync_repairs(report: Any, backend_type: str | None) -> bool:
392
+ """Return True when GitHub-specific post-sync repairs should run."""
393
+ return bool(
394
+ backend_type
395
+ and backend_type.lower() == "github"
396
+ and getattr(report, "issues_pulled", 0) > 0
397
+ )
398
+
399
+
400
+ def _sync_post_pull_cache(core: Any, console_inst: Any) -> None:
401
+ """Sync pulled file changes into DB cache when possible."""
402
+ try:
403
+ db = getattr(core, "db", None)
404
+ roadmap_dir = getattr(core, "roadmap_dir", None)
405
+ if db and roadmap_dir:
406
+ sync_result = db.sync_directory_incremental(roadmap_dir)
407
+ synced = sync_result.get("files_synced", 0)
408
+ console_inst.print(f"[dim]DB cache sync: {synced} file(s) synced[/dim]")
409
+ return
410
+
411
+ logger.warning(
412
+ "post_sync_db_cache_skipped",
413
+ reason="missing_db_or_path",
414
+ has_db=bool(db),
415
+ has_roadmap_dir=bool(roadmap_dir),
416
+ severity="operational",
417
+ )
418
+ except Exception as e:
419
+ logger.warning(
420
+ "post_sync_db_cache_failed",
421
+ error=str(e),
422
+ error_type=type(e).__name__,
423
+ severity="operational",
424
+ )
425
+
426
+
427
+ def _repair_post_sync_remote_links(
428
+ core: Any,
429
+ console_inst: Any,
430
+ backend_type: str | None,
431
+ ) -> None:
432
+ """Repair remote links after successful pull from GitHub backend."""
433
+ try:
434
+ from roadmap.adapters.cli.sync_context import _repair_remote_links
435
+
436
+ backend_name = str(backend_type)
437
+ _repair_remote_links(core, console_inst, backend_name, dry_run=False)
438
+ except Exception as e:
439
+ logger.warning(
440
+ "post_sync_remote_link_repair_failed",
441
+ error=str(e),
442
+ error_type=type(e).__name__,
443
+ severity="operational",
444
+ )
445
+
446
+
447
+ def _run_post_sync_repairs(
448
+ core: Any,
449
+ console_inst: Any,
450
+ report: Any,
451
+ backend_type: str | None,
452
+ ) -> None:
453
+ """Run GitHub-specific cache and link repair operations after sync."""
454
+ if not _should_run_post_sync_repairs(report, backend_type):
455
+ return
456
+ _sync_post_pull_cache(core, console_inst)
457
+ _repair_post_sync_remote_links(core, console_inst, backend_type)
458
+
459
+
460
+ def _save_and_display_sync_metrics(core: Any, console_inst: Any, report: Any) -> None:
461
+ """Persist and display sync metrics when present in report."""
462
+ metrics = getattr(report, "metrics", None)
463
+ if not metrics:
464
+ return
465
+
466
+ try:
467
+ from roadmap.adapters.persistence.sync_metrics_repository import (
468
+ SyncMetricsRepository,
469
+ )
470
+
471
+ db_manager = getattr(core, "db_manager", None)
472
+ if db_manager is None and hasattr(core, "db"):
473
+ db_manager = getattr(core.db, "_db_manager", None)
474
+ if db_manager is None:
475
+ raise AttributeError("No database manager available")
476
+ metrics_repo = SyncMetricsRepository(db_manager)
477
+ metrics_repo.save(metrics)
478
+ except Exception as e:
479
+ logger.warning(
480
+ "failed_to_save_sync_metrics",
481
+ error=str(e),
482
+ severity="operational",
483
+ )
484
+
485
+ metrics_dict = metrics.to_dict() if hasattr(metrics, "to_dict") else metrics
486
+ _display_sync_metrics(console_inst, metrics_dict)
487
+
488
+
489
+ def run_analysis_phase(
490
+ orchestrator: Any,
491
+ push: bool,
492
+ pull: bool,
493
+ dry_run: bool,
494
+ verbose: bool,
495
+ console_inst: Any,
496
+ interactive_duplicates: bool = False,
497
+ ):
498
+ """Run analysis phase using orchestrator and present results."""
499
+ from roadmap.adapters.cli.sync_presenter import present_analysis
500
+
501
+ _ = dry_run # Reserved for future use in analysis phase
502
+ console_inst.print("\nšŸ“Š Analyzing sync status...", style="bold cyan")
503
+
504
+ with Progress(
505
+ SpinnerColumn(),
506
+ TextColumn("[progress.description]{task.description}"),
507
+ console=console_inst,
508
+ transient=True,
509
+ ) as progress:
510
+ task = progress.add_task("Comparing local, remote, and baseline...", total=None)
511
+
512
+ plan, analysis_report = orchestrator.analyze_all_issues(
513
+ push_only=push,
514
+ pull_only=pull,
515
+ interactive_duplicates=interactive_duplicates,
516
+ )
517
+
518
+ progress.update(task, description="Analysis complete")
519
+
520
+ present_analysis(analysis_report, verbose=verbose)
521
+
522
+ return plan, analysis_report