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,204 @@
1
+ """Service for fetching milestones from GitHub API."""
2
+
3
+ from datetime import datetime
4
+
5
+ from structlog import get_logger
6
+
7
+ from roadmap.adapters.sync.backends.github_client import GitHubClientWrapper
8
+ from roadmap.common.logging import log_error_with_context
9
+ from roadmap.core.models.sync_models import SyncMilestone
10
+
11
+ logger = get_logger()
12
+
13
+
14
+ class GitHubMilestoneFetchService:
15
+ """Handles fetching milestones from GitHub API."""
16
+
17
+ def __init__(
18
+ self,
19
+ github_client: GitHubClientWrapper,
20
+ config: dict,
21
+ ):
22
+ """Initialize milestone fetch service.
23
+
24
+ Args:
25
+ github_client: GitHubClientWrapper for API access
26
+ config: Configuration dict with 'owner', 'repo'
27
+ """
28
+ self.github_client = github_client
29
+ self.config = config
30
+
31
+ def get_milestones(self, state: str = "all") -> dict[str, SyncMilestone]:
32
+ """Fetch all milestones from GitHub.
33
+
34
+ Args:
35
+ state: Milestone state filter ('open', 'closed', 'all')
36
+
37
+ Returns:
38
+ Dict mapping milestone numbers (as strings) to SyncMilestone objects,
39
+ or empty dict if fetch fails.
40
+ """
41
+ if not self.github_client:
42
+ logger.warning("github_milestone_fetch_no_client")
43
+ return {}
44
+
45
+ owner = self.config.get("owner")
46
+ repo = self.config.get("repo")
47
+
48
+ if not owner or not repo:
49
+ logger.warning(
50
+ "github_milestone_fetch_missing_config", owner=owner, repo=repo
51
+ )
52
+ return {}
53
+
54
+ try:
55
+ logger.debug(
56
+ "github_milestone_fetch_start",
57
+ owner=owner,
58
+ repo=repo,
59
+ state=state,
60
+ )
61
+
62
+ # Get milestones using GitHub API
63
+ # Import handler and create with proper session
64
+ import requests
65
+
66
+ from roadmap.adapters.github.handlers.milestones import MilestoneHandler
67
+
68
+ # Create a requests session with authentication
69
+ session = requests.Session()
70
+ token = self.config.get("token")
71
+ if token:
72
+ session.headers.update(
73
+ {
74
+ "Authorization": f"token {token}",
75
+ "Accept": "application/vnd.github.v3+json",
76
+ "User-Agent": "roadmap-cli/1.0",
77
+ }
78
+ )
79
+
80
+ milestone_handler = MilestoneHandler(
81
+ session=session,
82
+ owner=owner,
83
+ repo=repo,
84
+ )
85
+
86
+ milestones_data = milestone_handler.get_milestones(state=state)
87
+
88
+ logger.info(
89
+ "github_milestones_fetched",
90
+ owner=owner,
91
+ repo=repo,
92
+ count=len(milestones_data),
93
+ )
94
+
95
+ # Convert each GitHub milestone to SyncMilestone
96
+ result = {}
97
+ failed_conversions = 0
98
+ for milestone_dict in milestones_data:
99
+ try:
100
+ sync_milestone = self._dict_to_sync_milestone(milestone_dict)
101
+ milestone_number = str(milestone_dict.get("number", ""))
102
+ result[milestone_number] = sync_milestone
103
+ except Exception as e:
104
+ failed_conversions += 1
105
+ milestone_number = (
106
+ milestone_dict.get("number")
107
+ if isinstance(milestone_dict, dict)
108
+ else "unknown"
109
+ )
110
+ log_error_with_context(
111
+ e,
112
+ operation="convert_github_milestone_to_sync",
113
+ entity_type="Milestone",
114
+ entity_id=str(milestone_number),
115
+ additional_context={
116
+ "owner": owner,
117
+ "repo": repo,
118
+ "total_milestones": len(milestones_data),
119
+ },
120
+ include_traceback=False,
121
+ )
122
+
123
+ if failed_conversions > 0:
124
+ logger.warning(
125
+ "github_milestone_conversions_failed",
126
+ owner=owner,
127
+ repo=repo,
128
+ total=len(milestones_data),
129
+ failed=failed_conversions,
130
+ successful=len(result),
131
+ )
132
+
133
+ return result
134
+
135
+ except Exception as e:
136
+ log_error_with_context(
137
+ e,
138
+ operation="fetch_github_milestones",
139
+ entity_type="Milestone",
140
+ additional_context={"owner": owner, "repo": repo, "state": state},
141
+ include_traceback=True,
142
+ )
143
+ return {}
144
+
145
+ def _dict_to_sync_milestone(self, milestone_dict: dict) -> SyncMilestone:
146
+ """Convert GitHub milestone dict to SyncMilestone.
147
+
148
+ Args:
149
+ milestone_dict: GitHub API milestone response
150
+
151
+ Returns:
152
+ SyncMilestone instance
153
+
154
+ Raises:
155
+ ValueError: If required fields are missing
156
+ """
157
+ milestone_number = milestone_dict.get("number")
158
+ if not milestone_number:
159
+ raise ValueError("GitHub milestone missing 'number' field")
160
+
161
+ title = milestone_dict.get("title")
162
+ if not title:
163
+ raise ValueError(f"GitHub milestone {milestone_number} missing 'title'")
164
+
165
+ # Parse state (GitHub uses "open", "closed")
166
+ state = milestone_dict.get("state", "open")
167
+
168
+ # Parse due date
169
+ due_date = None
170
+ due_on_str = milestone_dict.get("due_on")
171
+ if due_on_str:
172
+ try:
173
+ # GitHub returns ISO 8601 format with Z suffix
174
+ due_date = datetime.fromisoformat(due_on_str.replace("Z", "+00:00"))
175
+ except (ValueError, AttributeError) as e:
176
+ logger.debug(
177
+ "github_milestone_due_date_parse_failed",
178
+ milestone_number=milestone_number,
179
+ due_on_str=due_on_str,
180
+ error=str(e),
181
+ )
182
+
183
+ # Create SyncMilestone
184
+ sync_milestone = SyncMilestone(
185
+ id=f"gh_milestone_{milestone_number}",
186
+ name=title,
187
+ status=state,
188
+ headline=milestone_dict.get("description", "")[:100]
189
+ if milestone_dict.get("description")
190
+ else None,
191
+ due_date=due_date,
192
+ backend_name="github",
193
+ backend_id=milestone_number,
194
+ remote_ids={"github": milestone_number},
195
+ raw_response=milestone_dict,
196
+ metadata={
197
+ "open_issues": milestone_dict.get("open_issues", 0),
198
+ "closed_issues": milestone_dict.get("closed_issues", 0),
199
+ "created_at": milestone_dict.get("created_at"),
200
+ "updated_at": milestone_dict.get("updated_at"),
201
+ },
202
+ )
203
+
204
+ return sync_milestone
@@ -0,0 +1,198 @@
1
+ """Vanilla Git backend for self-hosting scenarios.
2
+
3
+ For self-hosted deployments (no remote database), this backend is a no-op.
4
+ The sync workflow is:
5
+ 1. User runs 'roadmap sync' to sync with GitHub API (via github_sync_backend)
6
+ 2. Sync creates/modifies .roadmap/issues/*.md files
7
+ 3. User manually runs 'git add' and 'git commit' to persist changes
8
+ 4. User manually runs 'git push' to push to git remote
9
+
10
+ This backend does not perform git operations - that's the user's responsibility.
11
+
12
+ Updated to use Result<T, SyncError> pattern for explicit error handling.
13
+ """
14
+
15
+ from typing import Any
16
+
17
+ from roadmap.common.result import Ok, Result
18
+ from roadmap.core.domain.issue import Issue
19
+ from roadmap.core.interfaces import (
20
+ SyncConflict,
21
+ SyncReport,
22
+ )
23
+ from roadmap.core.models.sync_models import (
24
+ SyncIssue,
25
+ SyncMilestone,
26
+ SyncProject,
27
+ )
28
+ from roadmap.core.services.sync.sync_errors import SyncError
29
+ from roadmap.infrastructure.coordination.core import RoadmapCore
30
+
31
+
32
+ class VanillaGitSyncBackend:
33
+ """No-op backend for self-hosting scenarios.
34
+
35
+ For self-hosted deployments without a remote database, this backend
36
+ delegates all operations to the user. The user is responsible for:
37
+ 1. Git add/commit of modified .roadmap/issues/ files
38
+ 2. Git push to sync with remote
39
+
40
+ This backend exists for interface compatibility but does not perform
41
+ any git operations.
42
+
43
+ Attributes:
44
+ core: RoadmapCore instance with access to local issues
45
+ config: Configuration dict (unused, kept for interface compatibility)
46
+ """
47
+
48
+ def __init__(self, core: RoadmapCore, config: dict[str, Any]):
49
+ """Initialize vanilla git sync backend.
50
+
51
+ Args:
52
+ core: RoadmapCore instance
53
+ config: Configuration dict (unused)
54
+ """
55
+ self.core = core
56
+ self.config = config
57
+
58
+ def get_backend_name(self) -> str:
59
+ """Get the canonical name of this backend.
60
+
61
+ Returns:
62
+ 'git' - used as key in Issue.remote_ids dict
63
+ """
64
+ return "git"
65
+
66
+ def authenticate(self) -> Result[bool, SyncError]:
67
+ """No-op authentication for self-hosting.
68
+
69
+ For self-hosted scenarios without a remote database, authentication
70
+ always succeeds - the user is responsible for git operations.
71
+
72
+ Returns:
73
+ Ok(True) - always succeeds for self-hosted scenario
74
+ """
75
+ return Ok(True)
76
+
77
+ def get_issues(self) -> Result[dict[str, SyncIssue], SyncError]:
78
+ """No-op: Get issues from remote.
79
+
80
+ For self-hosting without a remote database, always returns empty dict.
81
+ The user is responsible for syncing via git operations.
82
+
83
+ Returns:
84
+ Ok with empty dict (no remote issues in self-hosted scenario)
85
+ """
86
+ return Ok({})
87
+
88
+ def push_issues(self, local_issues: list[Issue]) -> Result[SyncReport, SyncError]:
89
+ """No-op: Push multiple issues.
90
+
91
+ For self-hosting, the user is responsible for git operations.
92
+
93
+ Args:
94
+ local_issues: List of Issue objects (unused)
95
+
96
+ Returns:
97
+ Ok with empty SyncReport (no-op)
98
+ """
99
+ return Ok(SyncReport())
100
+
101
+ def push_issue(self, local_issue: Issue) -> Result[bool, SyncError]:
102
+ """No-op: Push a single issue.
103
+
104
+ For self-hosting, the user is responsible for git operations.
105
+ Delegates to push_issues for consistency.
106
+
107
+ Args:
108
+ local_issue: The Issue object (unused)
109
+
110
+ Returns:
111
+ Ok(True) - no-op succeeds
112
+ """
113
+ report_result = self.push_issues([local_issue])
114
+ if report_result.is_err():
115
+ return report_result # type: ignore[return-value]
116
+
117
+ report = report_result.unwrap()
118
+ if len(report.pushed) > 0 or len(report.errors) == 0:
119
+ return Ok(True)
120
+ return Ok(True) # No-op always succeeds
121
+
122
+ def pull_issues(self, issue_ids: list[str]) -> Result[SyncReport, SyncError]:
123
+ """No-op: Pull specified remote issues.
124
+
125
+ For self-hosting, the user is responsible for git operations.
126
+
127
+ Args:
128
+ issue_ids: List of issue IDs to pull (unused)
129
+
130
+ Returns:
131
+ Ok with empty SyncReport (no-op)
132
+ """
133
+ return Ok(SyncReport())
134
+
135
+ def pull_issue(self, issue_id: str) -> Result[bool, SyncError]:
136
+ """No-op: Pull a single issue.
137
+
138
+ For self-hosting, the user is responsible for git operations.
139
+ Delegates to pull_issues for consistency.
140
+
141
+ Args:
142
+ issue_id: The issue ID (unused)
143
+
144
+ Returns:
145
+ Ok(True) - no-op succeeds
146
+ """
147
+ report_result = self.pull_issues([issue_id])
148
+ if report_result.is_err():
149
+ return report_result # type: ignore[return-value]
150
+
151
+ return Ok(True) # No-op always succeeds
152
+
153
+ def get_conflict_resolution_options(self, conflict: SyncConflict) -> list[str]:
154
+ """Get available resolution strategies.
155
+
156
+ For self-hosting, no conflict resolution options (no remote to conflict with).
157
+
158
+ Args:
159
+ conflict: The SyncConflict (unused)
160
+
161
+ Returns:
162
+ Empty list (no remote database)
163
+ """
164
+ return []
165
+
166
+ def resolve_conflict(self, conflict: SyncConflict, resolution: str) -> bool:
167
+ """No-op: Resolve a conflict.
168
+
169
+ For self-hosting, no conflicts can occur (no remote database).
170
+
171
+ Args:
172
+ conflict: The SyncConflict (unused)
173
+ resolution: The resolution strategy (unused)
174
+
175
+ Returns:
176
+ True (no-op succeeds)
177
+ """
178
+ return True
179
+
180
+ def get_milestones(self) -> Result[dict[str, SyncMilestone], SyncError]:
181
+ """No-op: Get milestones from remote.
182
+
183
+ For self-hosting without a remote database, always returns empty dict.
184
+
185
+ Returns:
186
+ Ok with empty dict (no remote milestones in self-hosted scenario)
187
+ """
188
+ return Ok({})
189
+
190
+ def get_projects(self) -> Result[dict[str, SyncProject], SyncError]:
191
+ """No-op: Get projects from remote.
192
+
193
+ For self-hosting without a remote database, always returns empty dict.
194
+
195
+ Returns:
196
+ Ok with empty dict (no remote projects in self-hosted scenario)
197
+ """
198
+ return Ok({})
@@ -0,0 +1,11 @@
1
+ """Shared services for sync backends."""
2
+
3
+ from .issue_persistence_service import IssuePersistenceService
4
+ from .issue_state_service import IssueStateService
5
+ from .sync_linking_service import SyncLinkingService
6
+
7
+ __all__ = [
8
+ "IssueStateService",
9
+ "IssuePersistenceService",
10
+ "SyncLinkingService",
11
+ ]
@@ -0,0 +1,83 @@
1
+ """Handles baseline state loading and updates for sync operations.
2
+
3
+ Extracted from SyncMergeEngine to separate baseline management concerns.
4
+ """
5
+
6
+ from dataclasses import fields
7
+ from datetime import UTC, datetime
8
+ from typing import Any
9
+
10
+ from structlog import get_logger
11
+
12
+ from roadmap.core.services.sync.sync_state import IssueBaseState, SyncState
13
+
14
+ logger = get_logger(__name__)
15
+
16
+
17
+ class BaselineStateHandler:
18
+ """Manages baseline state loading and updates."""
19
+
20
+ def __init__(self, core: Any, state_update_service: Any):
21
+ """Initialize with core services.
22
+
23
+ Args:
24
+ core: RoadmapCore instance with database access
25
+ state_update_service: SyncStateUpdateService for baseline updates
26
+ """
27
+ self.core = core
28
+ self.state_update_service = state_update_service
29
+
30
+ def load_baseline_state(self) -> SyncState | None:
31
+ """Load baseline state from database.
32
+
33
+ Returns:
34
+ SyncState object if baseline exists, None otherwise
35
+ """
36
+ try:
37
+ db_baseline = self.core.db.get_sync_baseline()
38
+ if db_baseline:
39
+ logger.debug(
40
+ "baseline_loaded_from_database", issue_count=len(db_baseline)
41
+ )
42
+ base_issues = {}
43
+ issue_field_names = {field.name for field in fields(IssueBaseState)}
44
+ for issue_id, data in db_baseline.items():
45
+ issue_kwargs = {
46
+ "id": issue_id,
47
+ "title": data.get("title", ""),
48
+ "assignee": data.get("assignee"),
49
+ "headline": data.get("headline", ""),
50
+ "content": data.get("content", ""),
51
+ "description": data.get("description", ""),
52
+ "labels": data.get("labels", []),
53
+ }
54
+ status_value = data.get("status", "todo")
55
+ if "status" in issue_field_names:
56
+ issue_kwargs["status"] = status_value
57
+ elif "state" in issue_field_names:
58
+ issue_kwargs["state"] = status_value
59
+
60
+ base_issues[issue_id] = IssueBaseState(**issue_kwargs)
61
+
62
+ sync_state = SyncState(
63
+ last_sync_time=datetime.now(UTC),
64
+ base_issues=base_issues,
65
+ )
66
+ return sync_state
67
+
68
+ logger.debug("baseline_not_found_in_database")
69
+ return None
70
+
71
+ except Exception as e:
72
+ logger.warning(
73
+ "baseline_load_failed", error=str(e), error_type=type(e).__name__
74
+ )
75
+ return None
76
+
77
+ def update_baseline_for_pulled(self, pulled_remote_ids: list[str]) -> None:
78
+ """Update baseline after pulling changes.
79
+
80
+ Args:
81
+ pulled_remote_ids: List of remote issue IDs that were pulled
82
+ """
83
+ self.state_update_service.update_baseline_for_pulled(pulled_remote_ids)
@@ -0,0 +1,85 @@
1
+ """Converts issue changes to conflicts for resolution.
2
+
3
+ Extracted from SyncMergeEngine to separate conflict conversion logic.
4
+ """
5
+
6
+ from structlog import get_logger
7
+
8
+ from roadmap.core.services.sync.sync_conflict_resolver import Conflict, ConflictField
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class ConflictConverter:
14
+ """Converts issue changes to conflict objects."""
15
+
16
+ def __init__(self, state_comparator):
17
+ """Initialize with state comparator.
18
+
19
+ Args:
20
+ state_comparator: SyncStateComparator for timestamp extraction
21
+ """
22
+ self.state_comparator = state_comparator
23
+
24
+ def convert_changes_to_conflicts(self, issue_changes: list) -> list[Conflict]:
25
+ """Convert issue changes with conflicts to conflict objects.
26
+
27
+ Args:
28
+ issue_changes: List of IssueChange objects with conflict info
29
+
30
+ Returns:
31
+ List of Conflict objects ready for resolution
32
+ """
33
+ conflicts: list[Conflict] = []
34
+
35
+ for change in issue_changes:
36
+ if (
37
+ not change.has_conflict
38
+ or not change.local_state
39
+ or not change.remote_state
40
+ ):
41
+ continue
42
+ try:
43
+ conflicting_fields = []
44
+ if change.local_changes:
45
+ for field_name, _change_info in change.local_changes.items():
46
+ if field_name in change.remote_changes:
47
+ conflict_field = ConflictField(
48
+ field_name=field_name,
49
+ local_value=change.local_state.__dict__.get(
50
+ field_name, None
51
+ ),
52
+ remote_value=change.remote_state.get(field_name),
53
+ local_updated=change.local_state.updated,
54
+ remote_updated=self.state_comparator._extract_timestamp(
55
+ change.remote_state, "updated_at"
56
+ ),
57
+ )
58
+ conflicting_fields.append(conflict_field)
59
+
60
+ if conflicting_fields:
61
+ conflict = Conflict(
62
+ issue_id=change.issue_id,
63
+ local_issue=change.local_state,
64
+ remote_issue=change.remote_state,
65
+ fields=conflicting_fields,
66
+ local_updated=change.local_state.updated,
67
+ remote_updated=self.state_comparator._extract_timestamp(
68
+ change.remote_state, "updated_at"
69
+ ),
70
+ )
71
+ conflicts.append(conflict)
72
+
73
+ except Exception as e:
74
+ logger.warning(
75
+ "conflict_conversion_failed",
76
+ issue_id=change.issue_id,
77
+ operation="analyze_conflicts",
78
+ error_type=type(e).__name__,
79
+ error=str(e),
80
+ is_recoverable=True,
81
+ suggested_action="skip_issue",
82
+ )
83
+ continue
84
+
85
+ return conflicts