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,652 @@
1
+ """State manager and database errors for persistence layer."""
2
+
3
+ import sqlite3
4
+ from datetime import UTC, datetime
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from roadmap.common.logging import get_logger
9
+
10
+ from ..database_manager import DatabaseManager
11
+ from ..file_synchronizer import FileSynchronizer
12
+ from ..repositories import (
13
+ IssueRepository,
14
+ MilestoneRepository,
15
+ ProjectRepository,
16
+ RemoteLinkRepository,
17
+ SyncStateRepository,
18
+ )
19
+ from .connection_manager import ConnectionManager
20
+ from .issue_storage import IssueStorage
21
+ from .milestone_storage import MilestoneStorage
22
+ from .project_storage import ProjectStorage
23
+ from .sync_state_storage import SyncStateStorage
24
+
25
+ if TYPE_CHECKING:
26
+ from roadmap.adapters.git.sync_monitor import GitSyncMonitor # noqa: F401
27
+
28
+ logger = get_logger(__name__)
29
+
30
+
31
+ class DatabaseError(Exception):
32
+ """Base exception for database operations."""
33
+
34
+ pass
35
+
36
+
37
+ class StateManager:
38
+ """SQLite-based state manager for roadmap data.
39
+
40
+ This class serves as a facade, delegating entity-specific operations
41
+ to specialized repository classes while maintaining connection management
42
+ and transaction handling.
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ db_path: str | Path | None = None,
48
+ git_sync_monitor: "GitSyncMonitor | None" = None,
49
+ ):
50
+ """Initialize the state manager.
51
+
52
+ Args:
53
+ db_path: Path to SQLite database file. Defaults to ~/.roadmap/roadmap.db
54
+ git_sync_monitor: Optional GitSyncMonitor for cache invalidation via git diff.
55
+ If provided, database will sync on access.
56
+ """
57
+ if db_path is None:
58
+ db_path = Path.home() / ".roadmap" / "roadmap.db"
59
+
60
+ self.db_path = Path(db_path)
61
+ self._git_sync_monitor = git_sync_monitor
62
+ self._db_manager = DatabaseManager(db_path)
63
+ self._file_synchronizer = FileSynchronizer(
64
+ self._db_manager._get_connection, self._db_manager.transaction
65
+ )
66
+
67
+ # Initialize repositories
68
+ self._project_repo = ProjectRepository(
69
+ self._db_manager._get_connection, self._db_manager.transaction
70
+ )
71
+ self._milestone_repo = MilestoneRepository(
72
+ self._db_manager._get_connection, self._db_manager.transaction
73
+ )
74
+ self._issue_repo = IssueRepository(
75
+ self._db_manager._get_connection, self._db_manager.transaction
76
+ )
77
+ self._sync_state_repo = SyncStateRepository(
78
+ self._db_manager._get_connection, self._db_manager.transaction
79
+ )
80
+ self._remote_link_repo = RemoteLinkRepository(
81
+ self._db_manager._get_connection, self._db_manager.transaction
82
+ )
83
+
84
+ # Initialize storage layers
85
+ self._connection_manager = ConnectionManager(self._db_manager)
86
+ self._project_storage = ProjectStorage(self._project_repo)
87
+ self._milestone_storage = MilestoneStorage(self._milestone_repo)
88
+ self._issue_storage = IssueStorage(self._issue_repo)
89
+ self._sync_state_storage = SyncStateStorage(
90
+ self._sync_state_repo, self._file_synchronizer
91
+ )
92
+
93
+ # Expose database manager's _local for backward compatibility with tests
94
+ self._local = self._db_manager._local
95
+
96
+ logger.info(
97
+ "Initializing state manager",
98
+ db_path=str(self.db_path),
99
+ has_git_monitor=git_sync_monitor is not None,
100
+ )
101
+
102
+ # Note: Remote links initialization moved to RoadmapCore.initialize_remote_links()
103
+ # because StateManager doesn't have access to roadmap_dir
104
+
105
+ # Connection management - delegate to ConnectionManager
106
+ def _get_connection(self) -> sqlite3.Connection:
107
+ """Get thread-local database connection."""
108
+ return self._connection_manager.get_connection()
109
+
110
+ def transaction(self):
111
+ """Context manager for database transactions."""
112
+ return self._connection_manager.transaction()
113
+
114
+ def _sync_git_state(self) -> None:
115
+ """Sync git state to database if GitSyncMonitor is configured.
116
+
117
+ This ensures the database cache is up-to-date with git before
118
+ any repository access. Called transparently before repo operations.
119
+ """
120
+ if self._git_sync_monitor is None:
121
+ return
122
+
123
+ try:
124
+ changes = self._git_sync_monitor.detect_changes()
125
+ if changes:
126
+ self._git_sync_monitor.sync_to_database(changes)
127
+ except Exception as e:
128
+ logger.warning(
129
+ "Git sync failed, proceeding with potentially stale cache",
130
+ error=str(e),
131
+ error_type=type(e).__name__,
132
+ severity="operational",
133
+ )
134
+
135
+ def _initialize_remote_links_from_yaml(self) -> None:
136
+ """Initialize database remote_links from YAML files on startup.
137
+
138
+ This is a one-time initialization that loads all remote_ids found in
139
+ YAML frontmatter into the database cache for fast O(1) lookups during
140
+ sync operations.
141
+
142
+ Only loads links if database is empty (first initialization or reset).
143
+
144
+ Note: This method is deprecated. Use initialize_remote_links(roadmap_dir) instead.
145
+ """
146
+ # This is kept for backward compatibility but does nothing
147
+ # The actual initialization is now done by initialize_remote_links()
148
+
149
+ def initialize_remote_links(self, roadmap_dir: Path | str) -> None:
150
+ """Initialize remote_links from YAML files in the given roadmap directory.
151
+
152
+ Args:
153
+ roadmap_dir: Path to the roadmap directory
154
+ """
155
+ self._initialize_remote_links_impl(roadmap_dir)
156
+
157
+ def _initialize_remote_links_impl(self, roadmap_dir: Path | str) -> None:
158
+ """Initialize remote links from storage.
159
+
160
+ Args:
161
+ roadmap_dir: Path to the roadmap directory
162
+ """
163
+ try:
164
+ # Check if remote_links table already has data (skip if so)
165
+ existing_github_links = self._remote_link_repo.get_all_links_for_backend(
166
+ "github"
167
+ )
168
+ if existing_github_links:
169
+ logger.debug(
170
+ "remote_links_already_initialized",
171
+ existing_count=len(existing_github_links),
172
+ )
173
+ return
174
+
175
+ # Load all issue files from roadmap_dir/.roadmap/issues directory
176
+ from roadmap.adapters.persistence.parser.issue import IssueParser
177
+
178
+ issues_dir = Path(roadmap_dir) / ".roadmap" / "issues"
179
+
180
+ if not issues_dir.exists():
181
+ logger.debug("issues_directory_not_found", path=str(issues_dir))
182
+ return
183
+
184
+ # Collect all issue files from all subdirectories
185
+ issue_files = list(issues_dir.glob("**/*.md"))
186
+ if not issue_files:
187
+ logger.debug("no_issue_files_found", path=str(issues_dir))
188
+ return
189
+
190
+ # Build dict of issue_uuid -> dict[backend_name -> remote_id]
191
+ issues_with_remote_ids = {}
192
+ for file_path in issue_files:
193
+ try:
194
+ issue = IssueParser.parse_issue_file(file_path)
195
+ if issue.remote_ids:
196
+ issues_with_remote_ids[issue.id] = issue.remote_ids
197
+ except Exception as e:
198
+ logger.warning(
199
+ "failed_to_parse_issue_file",
200
+ file_path=str(file_path),
201
+ error=str(e),
202
+ )
203
+ continue
204
+
205
+ if not issues_with_remote_ids:
206
+ logger.debug("no_remote_ids_found_in_yaml_files")
207
+ return
208
+
209
+ # Bulk import into database
210
+ count = self._remote_link_repo.bulk_import_from_yaml(issues_with_remote_ids)
211
+ logger.info(
212
+ "initialized_remote_links_from_yaml",
213
+ total_files=len(issue_files),
214
+ issues_with_links=len(issues_with_remote_ids),
215
+ links_imported=count,
216
+ )
217
+
218
+ except Exception as e:
219
+ logger.error(
220
+ "failed_to_initialize_remote_links",
221
+ error=str(e),
222
+ error_type=type(e).__name__,
223
+ )
224
+ # Don't raise - allow startup to proceed even if initialization fails
225
+
226
+ def _init_database(self):
227
+ """Initialize database schema."""
228
+ # Delegated to DatabaseManager during initialization
229
+ pass
230
+
231
+ def _run_migrations(self):
232
+ """Run database migrations for schema updates."""
233
+ # Delegated to DatabaseManager during initialization
234
+ pass
235
+
236
+ def is_initialized(self) -> bool:
237
+ """Check if database is properly initialized."""
238
+ return self._connection_manager.is_initialized()
239
+
240
+ def close(self):
241
+ """Close database connections."""
242
+ self._connection_manager.close()
243
+
244
+ def vacuum(self):
245
+ """Optimize database."""
246
+ self._connection_manager.vacuum()
247
+
248
+ def database_exists(self) -> bool:
249
+ """Check if database file exists and has tables."""
250
+ return self._connection_manager.database_exists()
251
+
252
+ # Repository access - with automatic git sync
253
+ def get_issue_repository(self) -> IssueRepository:
254
+ """Get issue repository after syncing git state.
255
+
256
+ Returns:
257
+ IssueRepository with database synced to latest git state
258
+ """
259
+ self._sync_git_state()
260
+ return self._issue_repo
261
+
262
+ def get_milestone_repository(self) -> MilestoneRepository:
263
+ """Get milestone repository after syncing git state.
264
+
265
+ Returns:
266
+ MilestoneRepository with database synced to latest git state
267
+ """
268
+ self._sync_git_state()
269
+ return self._milestone_repo
270
+
271
+ def get_project_repository(self) -> ProjectRepository:
272
+ """Get project repository after syncing git state.
273
+
274
+ Returns:
275
+ ProjectRepository with database synced to latest git state
276
+ """
277
+ self._sync_git_state()
278
+ return self._project_repo
279
+
280
+ # Project operations - delegate to ProjectStorage
281
+ def create_project(self, project_data: dict[str, Any]) -> str:
282
+ """Create a new project."""
283
+ return self._project_storage.create(project_data)
284
+
285
+ def get_project(self, project_id: str) -> dict[str, Any] | None:
286
+ """Get project by ID."""
287
+ return self._project_storage.get(project_id)
288
+
289
+ def list_projects(self) -> list[dict[str, Any]]:
290
+ """List all projects."""
291
+ return self._project_storage.list_all()
292
+
293
+ def update_project(self, project_id: str, updates: dict[str, Any]) -> bool:
294
+ """Update project."""
295
+ return self._project_storage.update(project_id, updates)
296
+
297
+ def delete_project(self, project_id: str) -> bool:
298
+ """Delete project and all related data."""
299
+ return self._project_storage.delete(project_id)
300
+
301
+ def mark_project_archived(self, project_id: str, archived: bool = True) -> bool:
302
+ """Mark a project as archived or unarchived."""
303
+ return self._project_storage.mark_archived(project_id, archived)
304
+
305
+ # Milestone operations - delegate to MilestoneStorage
306
+ def create_milestone(self, milestone_data: dict[str, Any]) -> str:
307
+ """Create a new milestone."""
308
+ return self._milestone_storage.create(milestone_data)
309
+
310
+ def get_milestone(self, milestone_id: str) -> dict[str, Any] | None:
311
+ """Get milestone by ID."""
312
+ return self._milestone_storage.get(milestone_id)
313
+
314
+ def update_milestone(self, milestone_id: str, updates: dict[str, Any]) -> bool:
315
+ """Update milestone."""
316
+ return self._milestone_storage.update(milestone_id, updates)
317
+
318
+ def mark_milestone_archived(self, milestone_id: str, archived: bool = True) -> bool:
319
+ """Mark a milestone as archived or unarchived."""
320
+ return self._milestone_storage.mark_archived(milestone_id, archived)
321
+
322
+ # Issue operations - delegate to IssueStorage
323
+ def create_issue(self, issue_data: dict[str, Any]) -> str:
324
+ """Create a new issue."""
325
+ return self._issue_storage.create(issue_data)
326
+
327
+ def get_issue(self, issue_id: str) -> dict[str, Any] | None:
328
+ """Get issue by ID."""
329
+ return self._issue_storage.get(issue_id)
330
+
331
+ def update_issue(self, issue_id: str, updates: dict[str, Any]) -> bool:
332
+ """Update issue."""
333
+ return self._issue_storage.update(issue_id, updates)
334
+
335
+ def delete_issue(self, issue_id: str) -> bool:
336
+ """Delete issue."""
337
+ return self._issue_storage.delete(issue_id)
338
+
339
+ def mark_issue_archived(self, issue_id: str, archived: bool = True) -> bool:
340
+ """Mark an issue as archived or unarchived."""
341
+ return self._issue_storage.mark_archived(issue_id, archived)
342
+
343
+ # Sync state operations - delegate to SyncStateStorage
344
+ def get_sync_state(self, key: str) -> str | None:
345
+ """Get sync state value."""
346
+ return self._sync_state_storage.get_sync_state(key)
347
+
348
+ def set_sync_state(self, key: str, value: str):
349
+ """Set sync state value."""
350
+ self._sync_state_storage.set_sync_state(key, value)
351
+
352
+ # Remote link operations - delegate to RemoteLinkRepository
353
+ @property
354
+ def remote_links(self) -> RemoteLinkRepository:
355
+ """Get remote link repository for sync backend operations."""
356
+ return self._remote_link_repo
357
+
358
+ # Sync baseline operations - store/retrieve baseline for three-way merge
359
+ def get_sync_baseline(self) -> dict[str, Any] | None:
360
+ """Get the sync baseline from database.
361
+
362
+ The baseline represents the state of issues as they were during the last
363
+ successful sync. Used for three-way merge to detect conflicts.
364
+
365
+ Returns:
366
+ Dictionary mapping issue_id to baseline state, or None if no baseline exists
367
+ """
368
+ try:
369
+ import json
370
+
371
+ self._sync_git_state()
372
+
373
+ conn = self._get_connection()
374
+ rows = conn.execute(
375
+ """
376
+ SELECT issue_id, status, assignee, milestone, description, headline, content, labels, synced_at
377
+ FROM sync_base_state
378
+ ORDER BY synced_at DESC
379
+ """
380
+ ).fetchall()
381
+
382
+ if not rows:
383
+ logger.info("sync_baseline_not_found_empty_database")
384
+ return None
385
+
386
+ baseline = {}
387
+ for row in rows:
388
+ try:
389
+ baseline[row["issue_id"]] = {
390
+ "status": row["status"],
391
+ "assignee": row["assignee"],
392
+ "milestone": row["milestone"],
393
+ "description": row["description"],
394
+ "headline": row["headline"],
395
+ "content": row["content"],
396
+ "labels": json.loads(row["labels"]) if row["labels"] else [],
397
+ "synced_at": row["synced_at"],
398
+ }
399
+ except json.JSONDecodeError as je:
400
+ logger.warning(
401
+ "sync_baseline_labels_parse_error",
402
+ issue_id=row["issue_id"],
403
+ labels_str=row["labels"],
404
+ error=str(je),
405
+ )
406
+ # Fall back to empty labels for this issue
407
+ baseline[row["issue_id"]] = {
408
+ "status": row["status"],
409
+ "assignee": row["assignee"],
410
+ "milestone": row["milestone"],
411
+ "description": row["description"],
412
+ "headline": row["headline"],
413
+ "content": row["content"],
414
+ "labels": [],
415
+ "synced_at": row["synced_at"],
416
+ }
417
+
418
+ logger.info(
419
+ "sync_baseline_retrieved_from_database",
420
+ issue_count=len(baseline),
421
+ synced_at=rows[0]["synced_at"] if rows else None,
422
+ )
423
+ return baseline
424
+
425
+ except Exception as e:
426
+ logger.error(
427
+ "error_loading_sync_baseline",
428
+ error=str(e),
429
+ error_type=type(e).__name__,
430
+ exc_info=True,
431
+ )
432
+ return None
433
+
434
+ def save_sync_baseline(self, baseline: dict[str, Any]) -> bool:
435
+ """Save the current sync baseline to database.
436
+
437
+ Called after a successful sync to establish the new baseline for the
438
+ next sync's three-way merge.
439
+
440
+ Args:
441
+ baseline: Dictionary mapping issue_id to state dict with keys:
442
+ status, assignee, milestone, headline, content, labels
443
+
444
+ Returns:
445
+ True if saved successfully, False otherwise
446
+ """
447
+ try:
448
+ import json
449
+
450
+ now = datetime.now(UTC).isoformat()
451
+
452
+ logger.debug(
453
+ "saving_sync_baseline_to_database",
454
+ issue_count=len(baseline),
455
+ )
456
+
457
+ # Get connection and disable FK constraints for this operation
458
+ conn = self._connection_manager.get_connection()
459
+ conn.execute("PRAGMA foreign_keys = OFF")
460
+
461
+ try:
462
+ conn.execute("BEGIN IMMEDIATE")
463
+ # Clear old baseline
464
+ conn.execute("DELETE FROM sync_base_state")
465
+ logger.debug("cleared_old_sync_baseline_from_database")
466
+
467
+ # Insert new baseline
468
+ for issue_id, state in baseline.items():
469
+ conn.execute(
470
+ """
471
+ INSERT INTO sync_base_state
472
+ (issue_id, status, assignee, milestone, description, headline, content, labels, synced_at)
473
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
474
+ """,
475
+ (
476
+ issue_id,
477
+ state.get("status"),
478
+ state.get("assignee"),
479
+ state.get("milestone"),
480
+ state.get("description"), # Keep for backwards compat
481
+ state.get("headline"),
482
+ state.get("content"),
483
+ json.dumps(state.get("labels", [])),
484
+ now,
485
+ ),
486
+ )
487
+ conn.execute("COMMIT")
488
+ finally:
489
+ # Re-enable foreign keys
490
+ conn.execute("PRAGMA foreign_keys = ON")
491
+
492
+ logger.info(
493
+ "sync_baseline_saved_to_database",
494
+ issue_count=len(baseline),
495
+ synced_at=now,
496
+ )
497
+ return True
498
+
499
+ except Exception as e:
500
+ logger.error(
501
+ "error_saving_sync_baseline",
502
+ error=str(e),
503
+ error_type=type(e).__name__,
504
+ issue_count=len(baseline),
505
+ exc_info=True,
506
+ )
507
+ return False
508
+
509
+ def clear_sync_baseline(self) -> bool:
510
+ """Clear the sync baseline (used before first sync).
511
+
512
+ Returns:
513
+ True if cleared successfully
514
+ """
515
+ try:
516
+ logger.debug("clearing_sync_baseline_from_database")
517
+
518
+ with self.transaction() as conn:
519
+ conn.execute("DELETE FROM sync_base_state")
520
+
521
+ logger.info("sync_baseline_cleared_from_database")
522
+ return True
523
+ except Exception as e:
524
+ logger.error(
525
+ "error_clearing_sync_baseline",
526
+ error=str(e),
527
+ error_type=type(e).__name__,
528
+ exc_info=True,
529
+ )
530
+ return False
531
+
532
+ # File sync delegation - delegate to SyncStateStorage
533
+ def get_file_sync_status(self, file_path: str) -> dict[str, Any] | None:
534
+ """Get sync status for a file."""
535
+ return self._sync_state_storage.get_file_sync_status(file_path)
536
+
537
+ def update_file_sync_status(
538
+ self, file_path: str, content_hash: str, file_size: int, last_modified: Any
539
+ ):
540
+ """Update sync status for a file."""
541
+ self._sync_state_storage.update_file_sync_status(
542
+ file_path, content_hash, file_size, last_modified
543
+ )
544
+
545
+ def has_file_changed(self, file_path: Path) -> bool:
546
+ """Check if file has changed since last sync."""
547
+ return self._sync_state_storage.has_file_changed(file_path)
548
+
549
+ def sync_directory_incremental(self, roadmap_dir: Path) -> dict[str, Any]:
550
+ """Incrementally sync .roadmap directory to database."""
551
+ return self._sync_state_storage.sync_directory_incremental(roadmap_dir)
552
+
553
+ def full_rebuild_from_git(self, roadmap_dir: Path) -> dict[str, Any]:
554
+ """Full rebuild of database from git files."""
555
+ return self._sync_state_storage.full_rebuild_from_git(roadmap_dir)
556
+
557
+ def should_do_full_rebuild(self, roadmap_dir: Path, threshold: int = 50) -> bool:
558
+ """Determine if full rebuild is needed vs incremental sync."""
559
+ return self._sync_state_storage.should_do_full_rebuild(roadmap_dir, threshold)
560
+
561
+ def smart_sync(self, roadmap_dir: Path | None = None) -> dict[str, Any]:
562
+ """Smart sync that chooses between incremental and full rebuild."""
563
+ return self._sync_state_storage.smart_sync(roadmap_dir)
564
+
565
+ # Query operations - these delegate complex queries to query service
566
+ def has_file_changes(self) -> bool:
567
+ """Check if .roadmap/ files have changes since last sync."""
568
+ return self._sync_state_storage.has_file_changes(self)
569
+
570
+ def get_all_issues(self) -> list[dict[str, Any]]:
571
+ """Get all issues from database."""
572
+ from .queries import QueryService
573
+
574
+ logger.debug("getting_all_issues")
575
+ issues = QueryService(self).get_all_issues()
576
+ logger.debug("get_all_issues_completed", issue_count=len(issues))
577
+ return issues
578
+
579
+ def get_all_milestones(self) -> list[dict[str, Any]]:
580
+ """Get all milestones from database."""
581
+ from .queries import QueryService
582
+
583
+ return QueryService(self).get_all_milestones()
584
+
585
+ def get_milestone_progress(self, milestone_name: str) -> dict[str, int]:
586
+ """Get progress stats for a milestone."""
587
+ from .queries import QueryService
588
+
589
+ return QueryService(self).get_milestone_progress(milestone_name)
590
+
591
+ def get_issues_by_status(self) -> dict[str, int]:
592
+ """Get issue counts by status."""
593
+ from .queries import QueryService
594
+
595
+ return QueryService(self).get_issues_by_status()
596
+
597
+ # Conflict operations - delegate to conflict service
598
+ def check_git_conflicts(self, roadmap_dir: Path | None = None) -> list[str]:
599
+ """Check for git conflicts in .roadmap directory."""
600
+ from .conflicts import ConflictService
601
+
602
+ return ConflictService(self).check_git_conflicts(roadmap_dir)
603
+
604
+ def has_git_conflicts(self) -> bool:
605
+ """Check if there are unresolved git conflicts."""
606
+ from .conflicts import ConflictService
607
+
608
+ return ConflictService(self).has_git_conflicts()
609
+
610
+ def get_conflict_files(self) -> list[str]:
611
+ """Get list of files with git conflicts."""
612
+ from .conflicts import ConflictService
613
+
614
+ return ConflictService(self).get_conflict_files()
615
+
616
+ def is_safe_for_writes(self) -> tuple[bool, str]:
617
+ """Check if database is safe for write operations."""
618
+ try:
619
+ # Check for git conflicts
620
+ if self.has_git_conflicts():
621
+ conflict_files = self.get_conflict_files()
622
+ return (
623
+ False,
624
+ f"Git conflicts detected in {len(conflict_files)} files. Resolve conflicts first.",
625
+ )
626
+
627
+ # Delegate to connection manager for safety check
628
+ return self._connection_manager.is_safe_for_writes()
629
+
630
+ except Exception as e:
631
+ return False, f"Safety check failed: {e}"
632
+
633
+
634
+ # Global state manager instance
635
+ _state_manager: StateManager | None = None
636
+
637
+
638
+ def get_state_manager(db_path: str | Path | None = None) -> StateManager:
639
+ """Get the global state manager instance."""
640
+ global _state_manager
641
+
642
+ if _state_manager is None:
643
+ _state_manager = StateManager(db_path)
644
+
645
+ return _state_manager
646
+
647
+
648
+ def initialize_state_manager(db_path: str | Path | None = None) -> StateManager:
649
+ """Initialize the global state manager."""
650
+ global _state_manager
651
+ _state_manager = StateManager(db_path)
652
+ return _state_manager