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,112 @@
1
+ """Service for creating local issues from remote data."""
2
+
3
+ from structlog import get_logger
4
+
5
+ from roadmap.core.domain.issue import Issue, Status
6
+ from roadmap.core.models.sync_models import SyncIssue
7
+ from roadmap.infrastructure.coordination.core import RoadmapCore
8
+
9
+ logger = get_logger()
10
+
11
+
12
+ class RemoteIssueCreationService:
13
+ """Handles creating local issues from remote issue data."""
14
+
15
+ def __init__(self, core: RoadmapCore):
16
+ """Initialize creation service.
17
+
18
+ Args:
19
+ core: RoadmapCore instance
20
+ """
21
+ self.core = core
22
+
23
+ def create_issue_from_remote(
24
+ self, remote_id: str | int, remote_issue: SyncIssue
25
+ ) -> Issue:
26
+ """Create a local Issue from remote SyncIssue data.
27
+
28
+ Extracts relevant fields from remote issue and creates a local Issue object.
29
+ Adds "synced:from-github" label to mark as synced from remote.
30
+ Uses remote milestone if available, otherwise defaults to backlog.
31
+
32
+ Args:
33
+ remote_id: Remote issue ID (number)
34
+ remote_issue: SyncIssue object with remote data including:
35
+ - title: Issue title
36
+ - headline: Short description
37
+ - status: 'open', 'closed', etc.
38
+ - labels: List of label names
39
+ - assignee: Assignee login
40
+ - milestone: Milestone title or None
41
+ - backend_id: GitHub issue number
42
+
43
+ Returns:
44
+ New Issue object ready to be created
45
+ """
46
+ try:
47
+ # Normalize status
48
+ issue_status = self._normalize_status(remote_issue.status)
49
+
50
+ # Prepare labels with synced marker
51
+ labels = list(remote_issue.labels or [])
52
+ if "synced:from-github" not in labels:
53
+ labels.append("synced:from-github")
54
+
55
+ # Create the issue
56
+ new_issue = Issue(
57
+ title=remote_issue.title or f"Remote Issue {remote_id}",
58
+ content=remote_issue.headline or "",
59
+ status=issue_status,
60
+ labels=labels,
61
+ assignee=remote_issue.assignee,
62
+ milestone=remote_issue.milestone or "backlog",
63
+ )
64
+
65
+ # Link to remote
66
+ if remote_issue.backend_id:
67
+ new_issue.remote_ids["github"] = remote_issue.backend_id
68
+
69
+ logger.info(
70
+ "remote_issue_created_locally",
71
+ remote_id=remote_id,
72
+ title=remote_issue.title,
73
+ local_status=issue_status,
74
+ )
75
+
76
+ return new_issue
77
+
78
+ except Exception as e:
79
+ logger.error(
80
+ "remote_issue_creation_failed",
81
+ remote_id=remote_id,
82
+ error=str(e),
83
+ error_type=type(e).__name__,
84
+ )
85
+ raise
86
+
87
+ @staticmethod
88
+ def _normalize_status(remote_status: str | None) -> Status:
89
+ """Normalize remote status to local Status enum.
90
+
91
+ Args:
92
+ remote_status: Remote status string (e.g., 'open', 'closed')
93
+
94
+ Returns:
95
+ Local Status enum value
96
+ """
97
+ if not remote_status:
98
+ return Status.TODO
99
+
100
+ remote_lower = remote_status.lower()
101
+
102
+ # Map common remote statuses to local statuses
103
+ status_map = {
104
+ "open": Status.TODO,
105
+ "in_progress": Status.IN_PROGRESS,
106
+ "in progress": Status.IN_PROGRESS,
107
+ "done": Status.CLOSED,
108
+ "closed": Status.CLOSED,
109
+ "blocked": Status.BLOCKED,
110
+ }
111
+
112
+ return status_map.get(remote_lower, Status.TODO)
@@ -0,0 +1,250 @@
1
+ """Service for analyzing and classifying sync changes."""
2
+
3
+ from datetime import UTC, datetime
4
+
5
+ from structlog import get_logger
6
+
7
+ from roadmap.core.services.sync.sync_key_normalizer import normalize_remote_keys
8
+ from roadmap.core.services.sync.sync_state import IssueBaseState, SyncState
9
+ from roadmap.core.services.sync.sync_state_comparator import SyncStateComparator
10
+ from roadmap.core.services.sync.sync_state_manager import SyncStateManager
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class SyncAnalysisService:
16
+ """Handles change analysis and classification for sync operations."""
17
+
18
+ def __init__(
19
+ self,
20
+ state_comparator: SyncStateComparator,
21
+ state_manager: SyncStateManager,
22
+ core=None,
23
+ ):
24
+ """Initialize analysis service.
25
+
26
+ Args:
27
+ state_comparator: Service for detecting changes
28
+ state_manager: Service for managing baseline state
29
+ core: Optional RoadmapCore for database baseline fallback
30
+ """
31
+ self.state_comparator = state_comparator
32
+ self.state_manager = state_manager
33
+ self.core = core
34
+
35
+ def load_baseline_safe(self):
36
+ """Load baseline state but swallow errors and return None on failure.
37
+
38
+ Returns:
39
+ SyncState or None if not found
40
+ """
41
+ try:
42
+ logger.debug("loading_sync_state")
43
+ base_state = self.state_manager.load_sync_state_from_db()
44
+ if base_state:
45
+ logger.info(
46
+ "previous_sync_state_loaded",
47
+ base_issues_count=len(base_state.base_issues),
48
+ last_sync=base_state.last_sync_time.isoformat()
49
+ if base_state.last_sync_time
50
+ else None,
51
+ )
52
+ else:
53
+ base_state = self._load_baseline_from_core()
54
+ if base_state:
55
+ logger.info(
56
+ "previous_sync_state_loaded",
57
+ base_issues_count=len(base_state.base_issues),
58
+ last_sync=base_state.last_sync_time.isoformat()
59
+ if base_state.last_sync_time
60
+ else None,
61
+ source="core_db",
62
+ )
63
+ else:
64
+ logger.info(
65
+ "no_previous_sync_state_found",
66
+ reason="first_sync_or_state_cleared",
67
+ )
68
+ except Exception:
69
+ logger.warning("sync_state_load_warning", reason="will_treat_as_first_sync")
70
+ base_state = None
71
+
72
+ return base_state
73
+
74
+ def _load_baseline_from_core(self) -> SyncState | None:
75
+ if not self.core or not getattr(self.core, "db", None):
76
+ return None
77
+
78
+ db_baseline = self.core.db.get_sync_baseline()
79
+ if not db_baseline:
80
+ return None
81
+
82
+ base_issues: dict[str, IssueBaseState] = {}
83
+ for issue_id, data in db_baseline.items():
84
+ base_issues[issue_id] = IssueBaseState(
85
+ id=issue_id,
86
+ status=data.get("status", "todo"),
87
+ title=data.get("title", ""),
88
+ assignee=data.get("assignee"),
89
+ headline=data.get("headline", ""),
90
+ content=data.get("content", ""),
91
+ labels=data.get("labels", []),
92
+ )
93
+
94
+ return SyncState(last_sync_time=datetime.now(UTC), base_issues=base_issues)
95
+
96
+ @staticmethod
97
+ def _is_empty_remote_value(value) -> bool:
98
+ if value is None:
99
+ return True
100
+ if isinstance(value, str):
101
+ return value.strip() == ""
102
+ if isinstance(value, (list, tuple, set, dict)):
103
+ return len(value) == 0
104
+ return False
105
+
106
+ @staticmethod
107
+ def _local_value_for_field(issue, field: str):
108
+ if issue is None:
109
+ return None
110
+ if field == "labels":
111
+ return issue.labels or []
112
+ if field == "assignee":
113
+ return issue.assignee
114
+ if field == "content":
115
+ return issue.content
116
+ return getattr(issue, field, None)
117
+
118
+ def _should_reclassify_as_push(self, change) -> bool:
119
+ if not change.remote_changes or not change.local_state:
120
+ return False
121
+ allowed_fields = {"labels", "assignee"}
122
+ for field, diff in change.remote_changes.items():
123
+ if field not in allowed_fields:
124
+ return False
125
+ remote_value = diff.get("to") if isinstance(diff, dict) else None
126
+ if not self._is_empty_remote_value(remote_value):
127
+ return False
128
+ local_value = self._local_value_for_field(change.local_state, field)
129
+ if self._is_empty_remote_value(local_value):
130
+ return False
131
+ return True
132
+
133
+ def _reclassify_remote_only_changes(self, remote_only_changes):
134
+ reclassified = [
135
+ c for c in remote_only_changes if self._should_reclassify_as_push(c)
136
+ ]
137
+ remaining = [c for c in remote_only_changes if c not in reclassified]
138
+ return reclassified, remaining
139
+
140
+ @staticmethod
141
+ def _extract_remote_id(remote_obj):
142
+ if isinstance(remote_obj, dict):
143
+ backend_id = remote_obj.get("backend_id") or remote_obj.get("number")
144
+ if backend_id is not None:
145
+ return str(backend_id)
146
+ remote_ids = remote_obj.get("remote_ids") or {}
147
+ backend_name = remote_obj.get("backend_name")
148
+ if backend_name and backend_name in remote_ids:
149
+ return str(remote_ids.get(backend_name))
150
+ if "github" in remote_ids:
151
+ return str(remote_ids.get("github"))
152
+ return None
153
+
154
+ backend_id = getattr(remote_obj, "backend_id", None)
155
+ if backend_id is not None:
156
+ return str(backend_id)
157
+ backend_name = getattr(remote_obj, "backend_name", None)
158
+ remote_ids = getattr(remote_obj, "remote_ids", None) or {}
159
+ if backend_name and backend_name in remote_ids:
160
+ return str(remote_ids.get(backend_name))
161
+ if "github" in remote_ids:
162
+ return str(remote_ids.get("github"))
163
+ return None
164
+
165
+ def _build_pull_ids(self, remote_only_changes, normalized_remote) -> list[str]:
166
+ pulls: list[str] = []
167
+ for change in remote_only_changes:
168
+ if change.issue_id in normalized_remote:
169
+ remote_obj = normalized_remote[change.issue_id]
170
+ remote_id = self._extract_remote_id(remote_obj)
171
+ if remote_id:
172
+ pulls.append(remote_id)
173
+ continue
174
+
175
+ issue_id = str(change.issue_id)
176
+ if issue_id.isdigit() or issue_id.startswith("_remote_"):
177
+ pulls.append(issue_id)
178
+ else:
179
+ logger.debug(
180
+ "pull_id_unresolved",
181
+ issue_id=change.issue_id,
182
+ reason="no_remote_id",
183
+ )
184
+ return pulls
185
+
186
+ def analyze_and_classify(self, local_issues_dict, remote_issues_data, base_state):
187
+ """Run comparator and classify changes into categories.
188
+
189
+ Args:
190
+ local_issues_dict: Dict of local Issue objects (id -> Issue)
191
+ remote_issues_data: Dict of remote issue data (keyed by remote ID)
192
+ base_state: Previous sync state or None
193
+
194
+ Returns:
195
+ Tuple of (changes, conflicts, local_only_changes, remote_only_changes,
196
+ no_changes, updates, pulls, up_to_date)
197
+ """
198
+ changes = self.state_comparator.analyze_three_way(
199
+ local_issues_dict,
200
+ remote_issues_data,
201
+ base_state.base_issues if base_state else None,
202
+ )
203
+
204
+ conflicts = [c for c in changes if c.has_conflict]
205
+ local_only_changes = [c for c in changes if c.is_local_only_change()]
206
+ remote_only_changes = [c for c in changes if c.is_remote_only_change()]
207
+ no_changes = [c for c in changes if c.conflict_type == "no_change"]
208
+
209
+ reclassified, remote_only_changes = self._reclassify_remote_only_changes(
210
+ remote_only_changes
211
+ )
212
+ if reclassified:
213
+ local_only_changes = local_only_changes + reclassified
214
+
215
+ updates = [c.local_state for c in local_only_changes if c.local_state]
216
+
217
+ normalized_remote = remote_issues_data
218
+ if self.state_comparator.backend is not None:
219
+ _, normalized_remote = normalize_remote_keys(
220
+ local_issues_dict,
221
+ remote_issues_data,
222
+ self.state_comparator.backend,
223
+ logger=logger,
224
+ )
225
+
226
+ # For pulls, we need to pass the remote ID (GitHub issue number) to pull_issues()
227
+ # Use normalized_remote so issue_id lookups match analysis keys.
228
+ pulls = self._build_pull_ids(remote_only_changes, normalized_remote)
229
+
230
+ up_to_date = [c.issue_id for c in no_changes]
231
+
232
+ logger.debug(
233
+ "three_way_analysis_complete",
234
+ total_changes=len(changes),
235
+ conflicts=len(conflicts),
236
+ local_only=len(local_only_changes),
237
+ remote_only=len(remote_only_changes),
238
+ no_change=len(no_changes),
239
+ )
240
+
241
+ return (
242
+ changes,
243
+ conflicts,
244
+ local_only_changes,
245
+ remote_only_changes,
246
+ no_changes,
247
+ updates,
248
+ pulls,
249
+ up_to_date,
250
+ )
@@ -0,0 +1,57 @@
1
+ """Service for handling sync authentication with pluggable backends.
2
+
3
+ Updated to use Result<T, SyncError> pattern for explicit error handling.
4
+ """
5
+
6
+ from structlog import get_logger
7
+
8
+ from roadmap.core.interfaces.sync_backend import SyncBackendInterface
9
+ from roadmap.core.services.sync.sync_report import SyncReport
10
+
11
+ logger = get_logger(__name__)
12
+
13
+
14
+ class SyncAuthenticationService:
15
+ """Handles backend authentication for sync operations."""
16
+
17
+ def __init__(self, backend: SyncBackendInterface):
18
+ """Initialize authentication service.
19
+
20
+ Args:
21
+ backend: SyncBackendInterface implementation
22
+ """
23
+ self.backend = backend
24
+
25
+ def ensure_authenticated(self, report: SyncReport) -> bool:
26
+ """Ensure backend is authenticated.
27
+
28
+ Args:
29
+ report: SyncReport to record any authentication errors
30
+
31
+ Returns:
32
+ True if authenticated, False otherwise
33
+
34
+ Notes:
35
+ Handles Result<bool, SyncError> from backend.authenticate()
36
+ """
37
+ result = self.backend.authenticate()
38
+
39
+ if result.is_ok():
40
+ logger.info("backend_authenticated_successfully")
41
+ return True
42
+
43
+ # Handle authentication error
44
+ error = result.unwrap_err()
45
+ report.error = str(error)
46
+
47
+ logger.error(
48
+ "backend_authentication_failed",
49
+ operation="authenticate",
50
+ backend_type=type(self.backend).__name__,
51
+ error_type=error.error_type.value,
52
+ error_message=error.message,
53
+ is_recoverable=error.is_recoverable,
54
+ suggested_action=error.suggested_fix or "check_credentials",
55
+ )
56
+
57
+ return False
@@ -0,0 +1,212 @@
1
+ """Service for fetching and counting sync data from local and remote sources.
2
+
3
+ Updated to use Result<T, SyncError> pattern for explicit error handling.
4
+ """
5
+
6
+ from structlog import get_logger
7
+
8
+ from roadmap.core.interfaces.sync_backend import SyncBackendInterface
9
+ from roadmap.core.services.sync.sync_report import SyncReport
10
+ from roadmap.infrastructure.coordination.core import RoadmapCore
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class SyncDataFetchService:
16
+ """Handles fetching issues and metadata from local and remote sources."""
17
+
18
+ def __init__(self, core: RoadmapCore, backend: SyncBackendInterface):
19
+ """Initialize data fetch service.
20
+
21
+ Args:
22
+ core: RoadmapCore instance
23
+ backend: SyncBackendInterface implementation
24
+ """
25
+ self.core = core
26
+ self.backend = backend
27
+
28
+ def fetch_remote_issues(self, report: SyncReport):
29
+ """Fetch remote issues from backend.
30
+
31
+ Args:
32
+ report: SyncReport to record any fetch errors
33
+
34
+ Returns:
35
+ Dict of remote issues or None if fetch failed
36
+
37
+ Notes:
38
+ Handles Result<dict, SyncError> from backend.get_issues()
39
+ """
40
+ logger.info("fetching_remote_issues_starting")
41
+
42
+ result = self.backend.get_issues()
43
+
44
+ if result.is_ok():
45
+ remote_issues_data = result.unwrap()
46
+
47
+ # Count by status
48
+ status_counts = {}
49
+ for issue in remote_issues_data.values():
50
+ status = getattr(issue, "state", None) or getattr(
51
+ issue, "status", "unknown"
52
+ )
53
+ status = str(status)
54
+ status_counts[status] = status_counts.get(status, 0) + 1
55
+
56
+ logger.info(
57
+ "remote_issues_enumerated",
58
+ total_count=len(remote_issues_data),
59
+ status_breakdown=status_counts,
60
+ )
61
+ return remote_issues_data
62
+
63
+ # Handle fetch error
64
+ error = result.unwrap_err()
65
+ report.error = str(error)
66
+
67
+ logger.error(
68
+ "remote_issues_fetch_error",
69
+ operation="fetch_remote_issues",
70
+ error_type=error.error_type.value,
71
+ error_message=error.message,
72
+ is_recoverable=error.is_recoverable,
73
+ suggested_action=error.suggested_fix or "check_backend_connectivity",
74
+ )
75
+
76
+ return None
77
+
78
+ def fetch_local_issues(self, report: SyncReport):
79
+ """Fetch local issues from roadmap.
80
+
81
+ Args:
82
+ report: SyncReport to record any fetch errors
83
+
84
+ Returns:
85
+ List of local Issue objects or None if fetch failed
86
+ """
87
+ try:
88
+ logger.info(
89
+ "fetching_local_issues_starting",
90
+ )
91
+ local_issues = self.core.issues.list_all_including_archived() or []
92
+
93
+ # Count by status
94
+ status_counts = {}
95
+ for issue in local_issues:
96
+ status = str(issue.status)
97
+ status_counts[status] = status_counts.get(status, 0) + 1
98
+
99
+ logger.info(
100
+ "local_issues_enumerated",
101
+ total_count=len(local_issues),
102
+ status_breakdown=status_counts,
103
+ )
104
+ return local_issues
105
+ except OSError as e:
106
+ report.error = f"Failed to fetch local issues: {str(e)}"
107
+ logger.error(
108
+ "local_issues_fetch_error",
109
+ operation="fetch_local_issues",
110
+ error_type=type(e).__name__,
111
+ error=str(e),
112
+ is_recoverable=False,
113
+ suggested_action="check_file_permissions",
114
+ )
115
+ return None
116
+ except Exception as e:
117
+ report.error = f"Failed to fetch local issues: {str(e)}"
118
+ logger.error(
119
+ "local_issues_fetch_error",
120
+ operation="fetch_local_issues",
121
+ error_type=type(e).__name__,
122
+ error=str(e),
123
+ error_classification="sync_error",
124
+ )
125
+ return None
126
+
127
+ def count_active_archived(self, local_issues):
128
+ """Count active and archived issues.
129
+
130
+ Args:
131
+ local_issues: List of Issue objects
132
+
133
+ Returns:
134
+ Tuple of (active_count, archived_count)
135
+ """
136
+ active_issues_count = 0
137
+ archived_issues_count = 0
138
+ for issue in local_issues:
139
+ if issue.file_path and "archive" in issue.file_path:
140
+ archived_issues_count += 1
141
+ else:
142
+ active_issues_count += 1
143
+ return active_issues_count, archived_issues_count
144
+
145
+ def count_milestones(self):
146
+ """Count active and archived milestones.
147
+
148
+ Returns:
149
+ Tuple of (all_milestones, active_count, archived_count)
150
+ """
151
+ try:
152
+ all_milestones = self.core.milestones.list()
153
+ active_milestones_count = 0
154
+ archived_milestones_count = 0
155
+ for milestone in all_milestones:
156
+ if (
157
+ hasattr(milestone, "file_path")
158
+ and milestone.file_path
159
+ and "archive" in milestone.file_path
160
+ ):
161
+ archived_milestones_count += 1
162
+ else:
163
+ active_milestones_count += 1
164
+ return all_milestones, active_milestones_count, archived_milestones_count
165
+ except Exception as e:
166
+ logger.debug("milestone_count_failed", error=str(e))
167
+ return [], 0, 0
168
+
169
+ def count_remote_stats(self, remote_issues_data):
170
+ """Count remote issues and milestones statistics.
171
+
172
+ Args:
173
+ remote_issues_data: Dict of remote issues
174
+
175
+ Returns:
176
+ Tuple of (total_count, open_count, closed_count, milestones_count)
177
+ """
178
+ try:
179
+ remote_issues_count = len(remote_issues_data) if remote_issues_data else 0
180
+ remote_open_count = 0
181
+ remote_closed_count = 0
182
+ for issue_data in remote_issues_data.values() if remote_issues_data else []:
183
+ state = getattr(issue_data, "state", None) or getattr(
184
+ issue_data, "status", "open"
185
+ )
186
+ if state and state.lower() == "closed":
187
+ remote_closed_count += 1
188
+ else:
189
+ remote_open_count += 1
190
+
191
+ milestones_result = self.backend.get_milestones()
192
+ remote_milestones_count = (
193
+ len(milestones_result.unwrap()) if milestones_result.is_ok() else 0
194
+ )
195
+ logger.debug(
196
+ "remote_items_counted",
197
+ remote_issues=remote_issues_count,
198
+ remote_open=remote_open_count,
199
+ remote_closed=remote_closed_count,
200
+ remote_milestones=remote_milestones_count,
201
+ )
202
+ return (
203
+ remote_issues_count,
204
+ remote_open_count,
205
+ remote_closed_count,
206
+ remote_milestones_count,
207
+ )
208
+ except Exception as e:
209
+ logger.error(
210
+ "count_remote_stats_error", error=str(e), severity="operational"
211
+ )
212
+ return 0, 0, 0, 0