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,274 @@
1
+ """Enhanced baseline building with progress display.
2
+
3
+ This module provides progress bar integration for sync state rebuilding,
4
+ allowing users to see real-time feedback during optimization phases.
5
+ """
6
+
7
+ from datetime import UTC, datetime
8
+ from pathlib import Path
9
+ from time import time
10
+
11
+ from structlog import get_logger
12
+
13
+ from roadmap.core.services.baseline.optimized_baseline_builder import (
14
+ CachedBaselineState,
15
+ OptimizedBaselineBuilder,
16
+ )
17
+ from roadmap.core.services.sync.sync_state import SyncState
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ class ProgressTrackingBaselineBuilder:
23
+ """Wraps OptimizedBaselineBuilder with progress bar support.
24
+
25
+ Provides real-time feedback during baseline rebuilding operations,
26
+ showing which phase is active (detection, rebuilding, merging).
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ issues_dir: Path,
32
+ show_progress: bool = True,
33
+ ):
34
+ """Initialize with progress tracking.
35
+
36
+ Args:
37
+ issues_dir: Path to issues directory
38
+ show_progress: Whether to show progress bars
39
+ """
40
+ self.builder = OptimizedBaselineBuilder(issues_dir)
41
+ self.show_progress = show_progress
42
+ self._progress = None
43
+ self._current_task = None
44
+
45
+ def set_progress_context(self, progress) -> None:
46
+ """Set progress context from rich.progress.Progress.
47
+
48
+ Args:
49
+ progress: Progress instance from rich library
50
+ """
51
+ self._progress = progress
52
+ self.builder.set_progress_context(progress)
53
+
54
+ def _log_phase(self, phase: str, details: dict | None = None) -> None:
55
+ """Log phase transition with progress.
56
+
57
+ Args:
58
+ phase: Name of phase
59
+ details: Optional details dict
60
+ """
61
+ logger.info(
62
+ "baseline_rebuild_phase",
63
+ phase=phase,
64
+ **(details or {}),
65
+ )
66
+
67
+ def rebuild_with_progress(
68
+ self,
69
+ all_issue_files: list[Path],
70
+ cached_state: SyncState | None,
71
+ git_ref: str = "HEAD~1",
72
+ ) -> tuple[dict[str, Path] | None, list[str] | None, CachedBaselineState]:
73
+ """Rebuild baseline with progress tracking.
74
+
75
+ Args:
76
+ all_issue_files: All current issue file paths
77
+ cached_state: Previous sync state (if any)
78
+ git_ref: Git reference for change detection
79
+
80
+ Returns:
81
+ Tuple of (issues_to_update, deleted_issues, metrics)
82
+ - issues_to_update: {issue_id: path} dict for issues to rebuild
83
+ - deleted_issues: [issue_id] list of deleted issues
84
+ - metrics: CachedBaselineState with rebuild stats
85
+ """
86
+ start_time = time()
87
+
88
+ try:
89
+ # Phase 1: Determine rebuild strategy
90
+ self._log_phase(
91
+ "Determining rebuild strategy",
92
+ {"cached": cached_state is not None},
93
+ )
94
+
95
+ should_full = self.builder.should_rebuild_all(
96
+ cached_state,
97
+ time_since_last_sync=None,
98
+ )
99
+
100
+ if should_full:
101
+ return self._rebuild_full_with_progress(all_issue_files, start_time)
102
+ elif cached_state is not None:
103
+ return self._rebuild_incremental_with_progress(
104
+ all_issue_files, cached_state, git_ref, start_time
105
+ )
106
+ else:
107
+ # No cached state and not full rebuild - treat as full
108
+ return self._rebuild_full_with_progress(all_issue_files, start_time)
109
+
110
+ except Exception as e:
111
+ logger.error(
112
+ "baseline_rebuild_error",
113
+ error=str(e),
114
+ error_type=type(e).__name__,
115
+ severity="system_error",
116
+ )
117
+ # Return empty rebuild (will fall back to default)
118
+ return (
119
+ None,
120
+ None,
121
+ CachedBaselineState(
122
+ SyncState(last_sync_time=datetime.now(UTC), base_issues={}),
123
+ from_cache=False,
124
+ rebuilt_issues=0,
125
+ reused_issues=0,
126
+ rebuild_time_ms=time() - start_time,
127
+ ),
128
+ )
129
+
130
+ def _rebuild_full_with_progress(
131
+ self,
132
+ all_issue_files: list[Path],
133
+ start_time: float,
134
+ ) -> tuple[dict[str, Path], list[str], CachedBaselineState]:
135
+ """Full rebuild with progress display.
136
+
137
+ Args:
138
+ all_issue_files: All files
139
+ start_time: Rebuild start time
140
+
141
+ Returns:
142
+ Tuple of (issues_to_update, [], metrics)
143
+ """
144
+ self._log_phase("Full rebuild", {"issue_count": len(all_issue_files)})
145
+
146
+ # All issues need rebuilding
147
+ issues_to_update = {}
148
+ for idx, file_path in enumerate(all_issue_files):
149
+ relative = str(file_path.relative_to(self.builder.issues_dir))
150
+ issue_id = self.builder.extract_issue_id_from_path(relative)
151
+ if issue_id:
152
+ issues_to_update[issue_id] = file_path
153
+
154
+ # Log progress every 10 items
155
+ if (idx + 1) % 10 == 0 or idx == 0:
156
+ self._log_phase(
157
+ f"Preparing rebuild ({idx + 1}/{len(all_issue_files)})",
158
+ {"prepared": idx + 1},
159
+ )
160
+
161
+ rebuild_time_ms = (time() - start_time) * 1000
162
+
163
+ self._log_phase(
164
+ f"Full rebuild ready ({len(issues_to_update)} issues)",
165
+ {"rebuild_time_ms": f"{rebuild_time_ms:.1f}"},
166
+ )
167
+
168
+ return (
169
+ issues_to_update,
170
+ [],
171
+ CachedBaselineState(
172
+ SyncState(
173
+ last_sync_time=datetime.now(UTC),
174
+ base_issues={},
175
+ ),
176
+ from_cache=False,
177
+ rebuilt_issues=len(issues_to_update),
178
+ reused_issues=0,
179
+ rebuild_time_ms=rebuild_time_ms,
180
+ ),
181
+ )
182
+
183
+ def _rebuild_incremental_with_progress(
184
+ self,
185
+ all_issue_files: list[Path],
186
+ cached_state: SyncState,
187
+ git_ref: str,
188
+ start_time: float,
189
+ ) -> tuple[dict[str, Path], list[str], CachedBaselineState]:
190
+ """Incremental rebuild with progress display.
191
+
192
+ Args:
193
+ all_issue_files: All files
194
+ cached_state: Previous state
195
+ git_ref: Git reference
196
+ start_time: Rebuild start time
197
+
198
+ Returns:
199
+ Tuple of (issues_to_update, deleted_issues, metrics)
200
+ """
201
+ self._log_phase("Detecting file changes", {"git_ref": git_ref})
202
+
203
+ # Phase 2: Get changed files
204
+ changed_files = self.builder.get_changed_issue_files(git_ref)
205
+ self._log_phase(
206
+ "File changes detected",
207
+ {"changed_count": len(changed_files)},
208
+ )
209
+
210
+ # Phase 3: Determine which issues need updates
211
+ self._log_phase(
212
+ f"Analyzing {len(all_issue_files)} issues",
213
+ {"cached_count": len(cached_state.base_issues)},
214
+ )
215
+
216
+ issues_to_update, deleted_issues = self.builder.get_incremental_update_issues(
217
+ all_issue_files, changed_files, cached_state
218
+ )
219
+
220
+ rebuild_time_ms = (time() - start_time) * 1000
221
+ reused_count = len(cached_state.base_issues) - len(issues_to_update)
222
+
223
+ self._log_phase(
224
+ "Rebuild analysis complete",
225
+ {
226
+ "rebuild_count": len(issues_to_update),
227
+ "reused_count": reused_count,
228
+ "deleted_count": len(deleted_issues),
229
+ "rebuild_time_ms": f"{rebuild_time_ms:.1f}",
230
+ },
231
+ )
232
+
233
+ return (
234
+ issues_to_update,
235
+ deleted_issues,
236
+ CachedBaselineState(
237
+ SyncState(
238
+ last_sync_time=cached_state.last_sync_time,
239
+ base_issues=cached_state.base_issues,
240
+ ),
241
+ from_cache=False,
242
+ rebuilt_issues=len(issues_to_update),
243
+ reused_issues=reused_count,
244
+ rebuild_time_ms=rebuild_time_ms,
245
+ ),
246
+ )
247
+
248
+
249
+ def create_progress_builder(
250
+ issues_dir: Path,
251
+ show_progress: bool = True,
252
+ ) -> ProgressTrackingBaselineBuilder | None:
253
+ """Create a progress-enabled baseline builder.
254
+
255
+ Args:
256
+ issues_dir: Path to issues directory
257
+ show_progress: Whether to show progress
258
+
259
+ Returns:
260
+ ProgressTrackingBaselineBuilder or None if progress not supported
261
+
262
+ Example:
263
+ >>> from rich.progress import Progress
264
+ >>> builder = create_progress_builder(Path("roadmap/issues"))
265
+ >>> with Progress() as progress:
266
+ ... builder.set_progress_context(progress)
267
+ ... updates, deleted, metrics = builder.rebuild_with_progress(files, state)
268
+ ... print(metrics.to_dict())
269
+ """
270
+ if not show_progress:
271
+ logger.debug("progress_bars_disabled")
272
+ return None
273
+
274
+ return ProgressTrackingBaselineBuilder(issues_dir, show_progress)
@@ -0,0 +1,63 @@
1
+ """Baseline snapshot retrieval from remote sources."""
2
+
3
+ from typing import Any
4
+
5
+ import structlog
6
+
7
+ BaselineSnapshot = dict[str, Any]
8
+
9
+ logger = structlog.get_logger()
10
+
11
+
12
+ class BaselineRetriever:
13
+ """Retrieve the current sync baseline from available stores.
14
+
15
+ Prefers `core.db.get_sync_baseline()` when available, then a provided
16
+ `db_session.get_sync_baseline()`. If neither is present, returns an
17
+ empty baseline snapshot.
18
+ """
19
+
20
+ @staticmethod
21
+ def get_current_baseline(
22
+ core: Any = None, db_session: Any = None
23
+ ) -> BaselineSnapshot:
24
+ """Get the current sync baseline from available stores.
25
+
26
+ Tries multiple sources in order: core.db, db_session, or empty dict.
27
+
28
+ Args:
29
+ core: Optional core instance with db attribute.
30
+ db_session: Optional database session.
31
+
32
+ Returns:
33
+ Baseline snapshot dictionary.
34
+ """
35
+ if (
36
+ core is not None
37
+ and getattr(core, "db", None)
38
+ and hasattr(core.db, "get_sync_baseline")
39
+ ):
40
+ try:
41
+ return core.db.get_sync_baseline() or {}
42
+ except Exception as e:
43
+ logger.debug(
44
+ "get_sync_baseline_failed",
45
+ operation="get_sync_baseline",
46
+ error=str(e),
47
+ action="Using fallback empty dict",
48
+ )
49
+ return {}
50
+
51
+ if db_session is not None and hasattr(db_session, "get_sync_baseline"):
52
+ try:
53
+ return db_session.get_sync_baseline() or {}
54
+ except Exception as e:
55
+ logger.debug(
56
+ "get_sync_baseline_db_failed",
57
+ operation="get_sync_baseline_db_session",
58
+ error=str(e),
59
+ action="Using empty baseline",
60
+ )
61
+ return {}
62
+
63
+ return {}
@@ -0,0 +1,248 @@
1
+ """Interactive baseline selection utility for team onboarding.
2
+
3
+ Provides guidance and interactive selection for teams creating their
4
+ initial baseline during first sync. Ensures explicit, informed choices
5
+ about the agreed-upon starting state.
6
+ """
7
+
8
+ from enum import StrEnum
9
+ from typing import Any
10
+
11
+ from structlog import get_logger
12
+
13
+ logger = get_logger()
14
+
15
+
16
+ class BaselineStrategy(StrEnum):
17
+ """Strategy for creating initial baseline."""
18
+
19
+ LOCAL = "local"
20
+ """Use local state as baseline - local code is source of truth."""
21
+
22
+ REMOTE = "remote"
23
+ """Use remote state as baseline - remote system is source of truth."""
24
+
25
+ INTERACTIVE = "interactive"
26
+ """Let user choose per-issue which state to use as baseline."""
27
+
28
+
29
+ class BaselineSelectionResult:
30
+ """Result of baseline selection for one or more issues."""
31
+
32
+ def __init__(
33
+ self,
34
+ strategy: BaselineStrategy,
35
+ selected_issues: dict[str, str] | None = None,
36
+ ):
37
+ """Initialize result.
38
+
39
+ Args:
40
+ strategy: The strategy chosen (LOCAL, REMOTE, or INTERACTIVE)
41
+ selected_issues: For INTERACTIVE, dict of issue_id -> "local" or "remote"
42
+ """
43
+ self.strategy = strategy
44
+ self.selected_issues = selected_issues or {}
45
+
46
+ def get_baseline_for_issue(
47
+ self,
48
+ issue_id: str,
49
+ local_issue: Any,
50
+ remote_issue: Any,
51
+ ) -> Any:
52
+ """Get the issue state to use as baseline.
53
+
54
+ Args:
55
+ issue_id: The issue ID
56
+ local_issue: The local issue object
57
+ remote_issue: The remote issue object
58
+
59
+ Returns:
60
+ The issue object/dict to use as baseline
61
+ """
62
+ if self.strategy == BaselineStrategy.LOCAL:
63
+ return local_issue
64
+ elif self.strategy == BaselineStrategy.REMOTE:
65
+ return remote_issue
66
+ elif self.strategy == BaselineStrategy.INTERACTIVE:
67
+ choice = self.selected_issues.get(issue_id, "local")
68
+ return local_issue if choice == "local" else remote_issue
69
+
70
+ return local_issue # Default to local
71
+
72
+
73
+ class InteractiveBaselineSelector:
74
+ """Guides teams through baseline selection during first sync."""
75
+
76
+ def __init__(self, console_output_fn=None):
77
+ """Initialize selector.
78
+
79
+ Args:
80
+ console_output_fn: Function to use for output (for testing)
81
+ """
82
+ self.console_output_fn = console_output_fn or self._default_output
83
+ self.logger = get_logger()
84
+
85
+ def _default_output(self, message: str):
86
+ """Output message to console."""
87
+ print(message)
88
+
89
+ def prompt_baseline_strategy(self) -> BaselineStrategy:
90
+ """Prompt team to choose baseline strategy.
91
+
92
+ Returns:
93
+ Selected BaselineStrategy
94
+ """
95
+ self.logger.info("baseline_strategy_prompt")
96
+
97
+ self.console_output_fn("\n" + "=" * 60)
98
+ self.console_output_fn("BASELINE SELECTION - First Sync Configuration")
99
+ self.console_output_fn("=" * 60)
100
+ self.console_output_fn(
101
+ """
102
+ This is your first sync. We need to establish the agreed-upon baseline state
103
+ that both local and remote systems accept as the starting point.
104
+
105
+ Choose one of these strategies:
106
+
107
+ 1. LOCAL - Use your local code as the baseline
108
+ Use this if: Local code is your source of truth
109
+ Effect: Remote will pull all local changes
110
+ Risk: May overwrite remote-only changes
111
+
112
+ 2. REMOTE - Use the remote system as the baseline
113
+ Use this if: Remote system is your source of truth
114
+ Effect: Local will pull all remote changes
115
+ Risk: May overwrite local-only changes
116
+
117
+ 3. INTERACTIVE - Choose per-issue which is the baseline
118
+ Use this if: Different issues have different sources of truth
119
+ Effect: You'll be asked for each changed issue
120
+ Risk: Most time-consuming but most flexible
121
+ """
122
+ )
123
+
124
+ while True:
125
+ choice = input("\nEnter your choice (1, 2, or 3): ").strip()
126
+ if choice == "1":
127
+ self.console_output_fn("✓ Selected: LOCAL baseline strategy")
128
+ self.logger.info("baseline_strategy_selected", strategy="local")
129
+ return BaselineStrategy.LOCAL
130
+ elif choice == "2":
131
+ self.console_output_fn("✓ Selected: REMOTE baseline strategy")
132
+ self.logger.info("baseline_strategy_selected", strategy="remote")
133
+ return BaselineStrategy.REMOTE
134
+ elif choice == "3":
135
+ self.console_output_fn("✓ Selected: INTERACTIVE baseline strategy")
136
+ self.logger.info("baseline_strategy_selected", strategy="interactive")
137
+ return BaselineStrategy.INTERACTIVE
138
+ else:
139
+ self.console_output_fn("Invalid choice. Please enter 1, 2, or 3.")
140
+
141
+ def prompt_interactive_selections(
142
+ self,
143
+ issues_with_changes: list[dict[str, Any]],
144
+ ) -> dict[str, str]:
145
+ """Prompt user to select baseline for each changed issue.
146
+
147
+ Args:
148
+ issues_with_changes: List of issues with dicts containing:
149
+ - issue_id: str
150
+ - title: str
151
+ - local_changes: dict or None
152
+ - remote_changes: dict or None
153
+
154
+ Returns:
155
+ Dict of issue_id -> "local" or "remote"
156
+ """
157
+ self.logger.info(
158
+ "interactive_baseline_selection_start",
159
+ issue_count=len(issues_with_changes),
160
+ )
161
+
162
+ selections = {}
163
+
164
+ self.console_output_fn("\n" + "=" * 60)
165
+ self.console_output_fn("INTERACTIVE BASELINE SELECTION")
166
+ self.console_output_fn("=" * 60)
167
+ self.console_output_fn(
168
+ f"\nFor each of {len(issues_with_changes)} changed issues, "
169
+ "choose which is the baseline:\n"
170
+ )
171
+
172
+ for idx, issue in enumerate(issues_with_changes, 1):
173
+ issue_id = issue.get("issue_id", "unknown")
174
+ title = issue.get("title", "Untitled")
175
+ local_changes = issue.get("local_changes", {})
176
+ remote_changes = issue.get("remote_changes", {})
177
+
178
+ self.console_output_fn(f"\n[{idx}/{len(issues_with_changes)}] {title}")
179
+ self.console_output_fn(f" ID: {issue_id}")
180
+
181
+ if local_changes:
182
+ self.console_output_fn(f" Local changes: {local_changes}")
183
+ if remote_changes:
184
+ self.console_output_fn(f" Remote changes: {remote_changes}")
185
+
186
+ choice = None
187
+ while choice is None:
188
+ response = (
189
+ input("\n Use LOCAL or REMOTE as baseline? (L/R): ")
190
+ .strip()
191
+ .upper()
192
+ )
193
+ if response in ("L", "LOCAL"):
194
+ choice = "local"
195
+ self.console_output_fn(" → Using LOCAL as baseline")
196
+ elif response in ("R", "REMOTE"):
197
+ choice = "remote"
198
+ self.console_output_fn(" → Using REMOTE as baseline")
199
+ else:
200
+ self.console_output_fn(" Invalid choice. Enter L or R.")
201
+
202
+ selections[issue_id] = choice
203
+ self.logger.debug(
204
+ "interactive_baseline_selected",
205
+ issue_id=issue_id,
206
+ choice=choice,
207
+ )
208
+
209
+ self.console_output_fn("\n✓ Baseline selection complete\n")
210
+ self.logger.info(
211
+ "interactive_baseline_selection_complete",
212
+ selections=selections,
213
+ )
214
+
215
+ return selections
216
+
217
+ def select_baseline(
218
+ self,
219
+ issues_with_changes: list[dict[str, Any]] | None = None,
220
+ strategy: BaselineStrategy | None = None,
221
+ ) -> BaselineSelectionResult:
222
+ """Guide complete baseline selection process.
223
+
224
+ Args:
225
+ issues_with_changes: Issues with changes (for interactive mode)
226
+ strategy: Override strategy (for testing/automation)
227
+
228
+ Returns:
229
+ BaselineSelectionResult with strategy and selections
230
+ """
231
+ self.logger.info("baseline_selection_start")
232
+
233
+ # Get strategy if not provided
234
+ if strategy is None:
235
+ strategy = self.prompt_baseline_strategy()
236
+
237
+ # Get specific selections if interactive
238
+ selected_issues = {}
239
+ if strategy == BaselineStrategy.INTERACTIVE and issues_with_changes:
240
+ selected_issues = self.prompt_interactive_selections(issues_with_changes)
241
+
242
+ result = BaselineSelectionResult(
243
+ strategy=strategy,
244
+ selected_issues=selected_issues,
245
+ )
246
+
247
+ self.logger.info("baseline_selection_complete", strategy=strategy.value)
248
+ return result