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,168 @@
1
+ """Repository for project persistence operations."""
2
+
3
+ from typing import Any
4
+
5
+ from roadmap.common.errors import OperationType, safe_operation
6
+ from roadmap.common.logging import get_logger
7
+
8
+ logger = get_logger(__name__)
9
+
10
+
11
+ class ProjectRepository:
12
+ """Handles all project-related database operations."""
13
+
14
+ def __init__(self, get_connection, transaction):
15
+ """Initialize repository with database connection methods.
16
+
17
+ Args:
18
+ get_connection: Callable that returns sqlite3 Connection
19
+ transaction: Context manager for database transactions
20
+ """
21
+ self._get_connection = get_connection
22
+ self._transaction = transaction
23
+
24
+ @safe_operation(OperationType.CREATE, "Project", include_traceback=True)
25
+ @safe_operation(OperationType.CREATE, "Project", include_traceback=True)
26
+ def create(self, project_data: dict[str, Any]) -> str:
27
+ """Create a new project.
28
+
29
+ Args:
30
+ project_data: Dictionary with project fields
31
+
32
+ Returns:
33
+ Project ID
34
+ """
35
+ with self._transaction() as conn:
36
+ conn.execute(
37
+ """
38
+ INSERT INTO projects (id, name, description, status, metadata)
39
+ VALUES (?, ?, ?, ?, ?)
40
+ """,
41
+ (
42
+ project_data["id"],
43
+ project_data["name"],
44
+ project_data.get("description"),
45
+ project_data.get("status", "active"),
46
+ project_data.get("metadata"),
47
+ ),
48
+ )
49
+
50
+ logger.info("Created project", project_id=project_data["id"])
51
+ return project_data["id"]
52
+
53
+ def get(self, project_id: str) -> dict[str, Any] | None:
54
+ """Get project by ID.
55
+
56
+ Args:
57
+ project_id: Project identifier
58
+
59
+ Returns:
60
+ Project data or None if not found
61
+ """
62
+ conn = self._get_connection()
63
+ row = conn.execute(
64
+ "SELECT * FROM projects WHERE id = ?", (project_id,)
65
+ ).fetchone()
66
+
67
+ return dict(row) if row else None
68
+
69
+ def list_all(self) -> list[dict[str, Any]]:
70
+ """List all projects.
71
+
72
+ Returns:
73
+ List of project data dictionaries
74
+ """
75
+ conn = self._get_connection()
76
+ rows = conn.execute(
77
+ "SELECT * FROM projects ORDER BY created_at DESC"
78
+ ).fetchall()
79
+ return [dict(row) for row in rows]
80
+
81
+ @safe_operation(OperationType.UPDATE, "Project")
82
+ @safe_operation(OperationType.UPDATE, "Project")
83
+ def update(self, project_id: str, updates: dict[str, Any]) -> bool:
84
+ """Update project.
85
+
86
+ Args:
87
+ project_id: Project identifier
88
+ updates: Dictionary of fields to update
89
+
90
+ Returns:
91
+ True if successful, False otherwise
92
+ """
93
+ if not updates:
94
+ return False
95
+
96
+ set_clause = ", ".join(f"{key} = ?" for key in updates.keys())
97
+ values = list(updates.values()) + [project_id]
98
+
99
+ with self._transaction() as conn:
100
+ cursor = conn.execute(
101
+ f"""
102
+ UPDATE projects SET {set_clause} WHERE id = ?
103
+ """,
104
+ values,
105
+ )
106
+
107
+ updated = cursor.rowcount > 0
108
+ if updated:
109
+ logger.info(
110
+ "Updated project", project_id=project_id, updates=list(updates.keys())
111
+ )
112
+
113
+ return updated
114
+
115
+ @safe_operation(OperationType.DELETE, "Project", include_traceback=True)
116
+ @safe_operation(OperationType.DELETE, "Project", include_traceback=True)
117
+ def delete(self, project_id: str) -> bool:
118
+ """Delete project and all related data.
119
+
120
+ Args:
121
+ project_id: Project identifier
122
+
123
+ Returns:
124
+ True if successful, False otherwise
125
+ """
126
+ with self._transaction() as conn:
127
+ cursor = conn.execute("DELETE FROM projects WHERE id = ?", (project_id,))
128
+
129
+ deleted = cursor.rowcount > 0
130
+ if deleted:
131
+ logger.info("Deleted project", project_id=project_id)
132
+
133
+ return deleted
134
+
135
+ @safe_operation(OperationType.UPDATE, "Project")
136
+ @safe_operation(OperationType.UPDATE, "Project")
137
+ def mark_archived(self, project_id: str, archived: bool = True) -> bool:
138
+ """Mark a project as archived or unarchived.
139
+
140
+ Args:
141
+ project_id: Project identifier
142
+ archived: True to archive, False to unarchive
143
+
144
+ Returns:
145
+ True if successful, False otherwise
146
+ """
147
+ with self._transaction() as conn:
148
+ if archived:
149
+ cursor = conn.execute(
150
+ "UPDATE projects SET archived = 1, archived_at = CURRENT_TIMESTAMP WHERE id = ?",
151
+ (project_id,),
152
+ )
153
+ else:
154
+ cursor = conn.execute(
155
+ "UPDATE projects SET archived = 0, archived_at = NULL WHERE id = ?",
156
+ (project_id,),
157
+ )
158
+
159
+ updated = cursor.rowcount > 0
160
+ if updated:
161
+ action = "archived" if archived else "unarchived"
162
+ logger.info(
163
+ "project_status_changed",
164
+ project_id=project_id,
165
+ action=action,
166
+ )
167
+
168
+ return updated
@@ -0,0 +1,355 @@
1
+ """Repository for managing issue-to-backend remote ID mappings.
2
+
3
+ Provides fast lookups and mutations for remote backend IDs stored in the
4
+ issue_remote_links database table. This enables efficient sync operations
5
+ without scanning YAML files.
6
+ """
7
+
8
+ from roadmap.common.logging import get_logger
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class RemoteLinkRepository:
14
+ """Handles issue remote link database operations.
15
+
16
+ Provides CRUD operations for tracking which issues are linked to
17
+ which remote backend IDs (e.g., GitHub issue numbers, GitLab MRs, etc).
18
+ """
19
+
20
+ def __init__(self, get_connection, transaction):
21
+ """Initialize repository with database connection methods.
22
+
23
+ Args:
24
+ get_connection: Callable that returns sqlite3 Connection
25
+ transaction: Context manager for database transactions
26
+ """
27
+ self._get_connection = get_connection
28
+ self._transaction = transaction
29
+
30
+ def link_issue(
31
+ self, issue_uuid: str, backend_name: str, remote_id: str | int
32
+ ) -> bool:
33
+ """Link an issue to a remote backend ID.
34
+
35
+ Args:
36
+ issue_uuid: Local issue UUID
37
+ backend_name: Backend name (e.g., 'github', 'gitlab')
38
+ remote_id: Remote ID in that backend (string or int)
39
+
40
+ Returns:
41
+ True if link created/updated, False if error
42
+ """
43
+ try:
44
+ remote_id_str = str(remote_id)
45
+ with self._transaction() as conn:
46
+ conn.execute(
47
+ """
48
+ INSERT OR REPLACE INTO issue_remote_links
49
+ (issue_uuid, backend_name, remote_id)
50
+ VALUES (?, ?, ?)
51
+ """,
52
+ (issue_uuid, backend_name, remote_id_str),
53
+ )
54
+ logger.debug(
55
+ "Linked issue to remote backend",
56
+ issue_uuid=issue_uuid,
57
+ backend_name=backend_name,
58
+ remote_id=remote_id_str,
59
+ )
60
+ return True
61
+ except Exception as e:
62
+ logger.warning(
63
+ "Failed to link issue",
64
+ issue_uuid=issue_uuid,
65
+ backend_name=backend_name,
66
+ remote_id=str(remote_id),
67
+ error_type=type(e).__name__,
68
+ error_message=str(e),
69
+ error_details=repr(e),
70
+ severity="operational",
71
+ )
72
+ return False
73
+
74
+ def unlink_issue(self, issue_uuid: str, backend_name: str) -> bool:
75
+ """Unlink an issue from a remote backend.
76
+
77
+ Args:
78
+ issue_uuid: Local issue UUID
79
+ backend_name: Backend name
80
+
81
+ Returns:
82
+ True if unlinked, False if error or not found
83
+ """
84
+ try:
85
+ with self._transaction() as conn:
86
+ cursor = conn.execute(
87
+ """
88
+ DELETE FROM issue_remote_links
89
+ WHERE issue_uuid = ? AND backend_name = ?
90
+ """,
91
+ (issue_uuid, backend_name),
92
+ )
93
+ if cursor.rowcount > 0:
94
+ logger.debug(
95
+ "Unlinked issue from remote backend",
96
+ issue_uuid=issue_uuid,
97
+ backend_name=backend_name,
98
+ )
99
+ return True
100
+ return False
101
+ except Exception as e:
102
+ logger.warning(
103
+ "Failed to unlink issue",
104
+ issue_uuid=issue_uuid,
105
+ backend_name=backend_name,
106
+ error_type=type(e).__name__,
107
+ error_message=str(e),
108
+ severity="operational",
109
+ )
110
+ return False
111
+
112
+ def get_remote_id(self, issue_uuid: str, backend_name: str) -> str | int | None:
113
+ """Get the remote ID for an issue on a specific backend.
114
+
115
+ Args:
116
+ issue_uuid: Local issue UUID
117
+ backend_name: Backend name (e.g., 'github')
118
+
119
+ Returns:
120
+ Remote ID (as string) or None if not linked
121
+ """
122
+ try:
123
+ conn = self._get_connection()
124
+ row = conn.execute(
125
+ """
126
+ SELECT remote_id FROM issue_remote_links
127
+ WHERE issue_uuid = ? AND backend_name = ?
128
+ """,
129
+ (issue_uuid, backend_name),
130
+ ).fetchone()
131
+ if row:
132
+ # Try to convert to int if it looks like one
133
+ remote_id = row["remote_id"]
134
+ try:
135
+ return int(remote_id)
136
+ except (ValueError, TypeError):
137
+ return remote_id
138
+ return None
139
+ except Exception as e:
140
+ logger.warning(
141
+ "Failed to get remote ID",
142
+ issue_uuid=issue_uuid,
143
+ backend_name=backend_name,
144
+ error_type=type(e).__name__,
145
+ error_message=str(e),
146
+ severity="operational",
147
+ )
148
+ return None
149
+
150
+ def get_issue_uuid(self, backend_name: str, remote_id: str | int) -> str | None:
151
+ """Reverse lookup: get local issue UUID from remote backend ID.
152
+
153
+ Args:
154
+ backend_name: Backend name (e.g., 'github')
155
+ remote_id: Remote ID in that backend
156
+
157
+ Returns:
158
+ Issue UUID or None if not found
159
+ """
160
+ try:
161
+ remote_id_str = str(remote_id)
162
+ conn = self._get_connection()
163
+ row = conn.execute(
164
+ """
165
+ SELECT issue_uuid FROM issue_remote_links
166
+ WHERE backend_name = ? AND remote_id = ?
167
+ """,
168
+ (backend_name, remote_id_str),
169
+ ).fetchone()
170
+ if row:
171
+ return row["issue_uuid"]
172
+ return None
173
+ except Exception as e:
174
+ logger.warning(
175
+ "Failed to get issue UUID",
176
+ backend_name=backend_name,
177
+ remote_id=str(remote_id),
178
+ error_type=type(e).__name__,
179
+ error_message=str(e),
180
+ )
181
+ return None
182
+
183
+ def get_all_links_for_issue(self, issue_uuid: str) -> dict[str, str | int]:
184
+ """Get all remote links for an issue (all backends).
185
+
186
+ Args:
187
+ issue_uuid: Local issue UUID
188
+
189
+ Returns:
190
+ Dict mapping backend_name -> remote_id
191
+ """
192
+ try:
193
+ conn = self._get_connection()
194
+ rows = conn.execute(
195
+ """
196
+ SELECT backend_name, remote_id FROM issue_remote_links
197
+ WHERE issue_uuid = ?
198
+ """,
199
+ (issue_uuid,),
200
+ ).fetchall()
201
+ result = {}
202
+ for row in rows:
203
+ backend_name = row["backend_name"]
204
+ remote_id_str = row["remote_id"]
205
+ # Try to convert to int if it looks like one
206
+ try:
207
+ result[backend_name] = int(remote_id_str)
208
+ except (ValueError, TypeError):
209
+ result[backend_name] = remote_id_str
210
+ return result
211
+ except Exception as e:
212
+ logger.warning(
213
+ "Failed to get all links for issue",
214
+ issue_uuid=issue_uuid,
215
+ error_type=type(e).__name__,
216
+ error_message=str(e),
217
+ )
218
+ return {}
219
+
220
+ def get_all_links_for_backend(self, backend_name: str) -> dict[str, str | int]:
221
+ """Get all remote links for a specific backend.
222
+
223
+ Args:
224
+ backend_name: Backend name (e.g., 'github')
225
+
226
+ Returns:
227
+ Dict mapping issue_uuid -> remote_id
228
+ """
229
+ try:
230
+ conn = self._get_connection()
231
+ rows = conn.execute(
232
+ """
233
+ SELECT issue_uuid, remote_id FROM issue_remote_links
234
+ WHERE backend_name = ?
235
+ """,
236
+ (backend_name,),
237
+ ).fetchall()
238
+ result = {}
239
+ for row in rows:
240
+ issue_uuid = row["issue_uuid"]
241
+ remote_id_str = row["remote_id"]
242
+ # Try to convert to int if it looks like one
243
+ try:
244
+ result[issue_uuid] = int(remote_id_str)
245
+ except (ValueError, TypeError):
246
+ result[issue_uuid] = remote_id_str
247
+ return result
248
+ except Exception as e:
249
+ logger.warning(
250
+ "Failed to get all links for backend",
251
+ backend_name=backend_name,
252
+ error_type=type(e).__name__,
253
+ error_message=str(e),
254
+ )
255
+ return {}
256
+
257
+ def validate_link(
258
+ self, issue_uuid: str, backend_name: str, remote_id: str | int
259
+ ) -> bool:
260
+ """Check if a link is valid (exists and matches remote_id).
261
+
262
+ Args:
263
+ issue_uuid: Local issue UUID
264
+ backend_name: Backend name
265
+ remote_id: Expected remote ID
266
+
267
+ Returns:
268
+ True if link exists and matches, False otherwise
269
+ """
270
+ try:
271
+ existing_id = self.get_remote_id(issue_uuid, backend_name)
272
+ if existing_id is None:
273
+ return False
274
+ # Compare as strings to handle int/str variations
275
+ return str(existing_id) == str(remote_id)
276
+ except Exception as e:
277
+ logger.warning(
278
+ "Failed to validate link",
279
+ issue_uuid=issue_uuid,
280
+ backend_name=backend_name,
281
+ error_type=type(e).__name__,
282
+ error_message=str(e),
283
+ )
284
+ return False
285
+
286
+ def bulk_import_from_yaml(
287
+ self, issues_with_remote_ids: dict[str, dict[str, str | int]]
288
+ ) -> int:
289
+ """Bulk import remote links from Issue objects with remote_ids.
290
+
291
+ This is typically called during initialization to sync existing
292
+ remote_ids from YAML files into the database.
293
+
294
+ Args:
295
+ issues_with_remote_ids: Dict mapping issue_uuid -> dict of backend_name -> remote_id
296
+
297
+ Returns:
298
+ Number of links successfully imported
299
+ """
300
+ count = 0
301
+ try:
302
+ with self._transaction() as conn:
303
+ for issue_uuid, remote_ids_dict in issues_with_remote_ids.items():
304
+ if not remote_ids_dict:
305
+ continue
306
+ for backend_name, remote_id in remote_ids_dict.items():
307
+ try:
308
+ remote_id_str = str(remote_id)
309
+ conn.execute(
310
+ """
311
+ INSERT OR IGNORE INTO issue_remote_links
312
+ (issue_uuid, backend_name, remote_id)
313
+ VALUES (?, ?, ?)
314
+ """,
315
+ (issue_uuid, backend_name, remote_id_str),
316
+ )
317
+ count += 1
318
+ except Exception as e:
319
+ logger.debug(
320
+ "Failed to import link",
321
+ issue_uuid=issue_uuid,
322
+ backend_name=backend_name,
323
+ remote_id=remote_id_str,
324
+ error_type=type(e).__name__,
325
+ error_message=str(e),
326
+ )
327
+ logger.info("Bulk imported remote links", count=count)
328
+ return count
329
+ except Exception as e:
330
+ logger.error(
331
+ "Bulk import of remote links failed",
332
+ count_imported=count,
333
+ error_type=type(e).__name__,
334
+ error_message=str(e),
335
+ )
336
+ return count
337
+
338
+ def clear_all(self) -> bool:
339
+ """Clear all remote links (useful for resetting/testing).
340
+
341
+ Returns:
342
+ True if successful, False if error
343
+ """
344
+ try:
345
+ with self._transaction() as conn:
346
+ conn.execute("DELETE FROM issue_remote_links")
347
+ logger.info("Cleared all remote links")
348
+ return True
349
+ except Exception as e:
350
+ logger.error(
351
+ "Failed to clear all remote links",
352
+ error_type=type(e).__name__,
353
+ error_message=str(e),
354
+ )
355
+ return False
@@ -0,0 +1,66 @@
1
+ """Repository for sync state persistence operations."""
2
+
3
+ from roadmap.common.logging import get_logger
4
+
5
+ logger = get_logger(__name__)
6
+
7
+
8
+ class SyncStateRepository:
9
+ """Handles all sync state database operations."""
10
+
11
+ def __init__(self, get_connection, transaction):
12
+ """Initialize repository with database connection methods.
13
+
14
+ Args:
15
+ get_connection: Callable that returns sqlite3 Connection
16
+ transaction: Context manager for database transactions
17
+ """
18
+ self._get_connection = get_connection
19
+ self._transaction = transaction
20
+
21
+ def get(self, key: str) -> str | None:
22
+ """Get sync state value by key.
23
+
24
+ Args:
25
+ key: State key
26
+
27
+ Returns:
28
+ State value or None if not found
29
+ """
30
+ conn = self._get_connection()
31
+ row = conn.execute(
32
+ "SELECT value FROM sync_metadata WHERE key = ?", (key,)
33
+ ).fetchone()
34
+ return row["value"] if row else None
35
+
36
+ def set(self, key: str, value: str) -> None:
37
+ """Set sync state value.
38
+
39
+ Args:
40
+ key: State key
41
+ value: State value
42
+ """
43
+ with self._transaction() as conn:
44
+ conn.execute(
45
+ """
46
+ INSERT OR REPLACE INTO sync_metadata (key, value) VALUES (?, ?)
47
+ """,
48
+ (key, value),
49
+ )
50
+ logger.debug("Set sync state", key=key)
51
+
52
+ def delete(self, key: str) -> None:
53
+ """Delete sync state value.
54
+
55
+ Args:
56
+ key: State key
57
+ """
58
+ with self._transaction() as conn:
59
+ conn.execute("DELETE FROM sync_metadata WHERE key = ?", (key,))
60
+ logger.debug("Deleted sync state", key=key)
61
+
62
+ def clear_all(self) -> None:
63
+ """Clear all sync state values."""
64
+ with self._transaction() as conn:
65
+ conn.execute("DELETE FROM sync_metadata")
66
+ logger.info("Cleared all sync state")
@@ -0,0 +1,36 @@
1
+ """Storage layer with modularized state management.
2
+
3
+ Organizes large query and conflict operations into separate service files:
4
+ - state_manager.py: Main StateManager facade (290 LOC after refactor)
5
+ - connection_manager.py: Database connection and transaction management (60 LOC)
6
+ - project_storage.py: Project CRUD operations (80 LOC)
7
+ - milestone_storage.py: Milestone CRUD operations (70 LOC)
8
+ - issue_storage.py: Issue CRUD operations (80 LOC)
9
+ - sync_state_storage.py: Sync state and file synchronization (180 LOC)
10
+ - queries.py: Complex database queries and aggregations (190 LOC)
11
+ - conflicts.py: Git conflict detection and handling (80 LOC)
12
+ """
13
+
14
+ from .connection_manager import ConnectionManager
15
+ from .issue_storage import IssueStorage
16
+ from .milestone_storage import MilestoneStorage
17
+ from .project_storage import ProjectStorage
18
+ from .state_manager import (
19
+ DatabaseError,
20
+ StateManager,
21
+ get_state_manager,
22
+ initialize_state_manager,
23
+ )
24
+ from .sync_state_storage import SyncStateStorage
25
+
26
+ __all__ = [
27
+ "ConnectionManager",
28
+ "DatabaseError",
29
+ "IssueStorage",
30
+ "MilestoneStorage",
31
+ "ProjectStorage",
32
+ "StateManager",
33
+ "SyncStateStorage",
34
+ "get_state_manager",
35
+ "initialize_state_manager",
36
+ ]