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,810 @@
1
+ """Enhanced sync orchestrator that uses git-based baselines.
2
+
3
+ This module extends SyncMergeOrchestrator with intelligent baseline management
4
+ using git history and YAML sync_metadata instead of database state tables.
5
+
6
+ Enforces explicit baseline creation during first sync via interactive selection.
7
+ """
8
+
9
+ from datetime import UTC, datetime
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from structlog import get_logger
14
+
15
+ from roadmap.adapters.persistence.parser.issue import IssueParser
16
+ from roadmap.adapters.sync.sync_merge_orchestrator import SyncMergeOrchestrator
17
+ from roadmap.core.models.sync_models import SyncIssue
18
+ from roadmap.core.services.baseline.baseline_selector import (
19
+ BaselineStrategy,
20
+ InteractiveBaselineSelector,
21
+ )
22
+ from roadmap.core.services.baseline.baseline_state_retriever import (
23
+ BaselineStateRetriever,
24
+ )
25
+ from roadmap.core.services.sync.sync_state import IssueBaseState, SyncState
26
+
27
+ logger = get_logger(__name__)
28
+
29
+
30
+ class SyncRetrievalOrchestrator(SyncMergeOrchestrator):
31
+ """Sync orchestrator using git history and YAML-based baseline retrieval.
32
+
33
+ This enhances the base SyncMergeOrchestrator with:
34
+ 1. Git history-based local baseline retrieval (no DB needed)
35
+ 2. YAML sync_metadata for remote baseline snapshots
36
+ 3. Intelligent baseline-less diff when no previous sync exists
37
+ """
38
+
39
+ def __init__(self, *args, persistence=None, **kwargs):
40
+ """Initialize with baseline retriever.
41
+
42
+ Args:
43
+ *args: Positional arguments passed to parent class
44
+ persistence: PersistenceInterface for git operations (optional)
45
+ **kwargs: Keyword arguments passed to parent class
46
+ """
47
+ super().__init__(*args, **kwargs)
48
+
49
+ # Use provided interfaces or create simple implementations
50
+ if persistence is None:
51
+ from roadmap.adapters.persistence.git_history import (
52
+ get_file_at_timestamp,
53
+ )
54
+ from roadmap.core.interfaces.persistence import PersistenceInterface
55
+
56
+ class SimplePersistence(PersistenceInterface):
57
+ def get_file_at_timestamp(self, file_path, timestamp):
58
+ return get_file_at_timestamp(file_path, timestamp)
59
+
60
+ def list_files_in_directory(self, directory):
61
+ from pathlib import Path
62
+
63
+ target_dir = Path(directory)
64
+ if not target_dir.exists():
65
+ return []
66
+ return [
67
+ f.name
68
+ for f in target_dir.iterdir()
69
+ if f.is_file() and f.suffix == ".md"
70
+ ]
71
+
72
+ persistence = SimplePersistence()
73
+
74
+ self.baseline_retriever = BaselineStateRetriever(
75
+ self.core.issues_dir, persistence
76
+ )
77
+ self.sync_metadata_cache: dict[str, Any] = {}
78
+ self.baseline_selector = InteractiveBaselineSelector()
79
+
80
+ @property
81
+ def issues_dir(self) -> Path:
82
+ """Get issues directory from core."""
83
+ # Navigate from roadmap_dir to issues directory
84
+ roadmap_dir = self.core.roadmap_dir
85
+ return roadmap_dir / "issues"
86
+
87
+ def has_baseline(self) -> bool:
88
+ """Check if a baseline has been established.
89
+
90
+ A baseline exists if:
91
+ 1. Baseline exists in database (Phase 3), OR
92
+ 2. Previous sync state can be loaded (legacy), OR
93
+ 3. Sync metadata with remote_state exists in issue files (legacy)
94
+
95
+ Returns:
96
+ True if baseline exists, False if first sync
97
+ """
98
+ try:
99
+ # PHASE 3: Check database baseline first (fast)
100
+ try:
101
+ db_baseline = self.core.db.get_sync_baseline()
102
+ if db_baseline:
103
+ logger.debug("baseline_exists_in_database")
104
+ return True
105
+ except Exception as e:
106
+ logger.debug("database_baseline_check_failed", error=str(e))
107
+
108
+ # Check if sync state can be loaded (legacy)
109
+ base_state = self.state_manager.load_sync_state()
110
+ if base_state and base_state.base_issues:
111
+ logger.debug("baseline_exists_from_sync_state")
112
+ return True
113
+
114
+ # Check if sync metadata exists in any issue (including archived)
115
+ try:
116
+ local_issues = self.core.issues.list_all_including_archived()
117
+ for issue in local_issues:
118
+ issue_file = self._find_issue_file(issue.id)
119
+ if not issue_file:
120
+ continue
121
+
122
+ metadata = IssueParser.load_sync_metadata(issue_file)
123
+ if metadata and "last_synced" in metadata:
124
+ logger.debug("baseline_exists_from_sync_metadata")
125
+ return True
126
+ except Exception as e:
127
+ logger.debug("metadata_check_failed", error=str(e))
128
+
129
+ logger.debug("no_baseline_found_first_sync")
130
+ return False
131
+
132
+ except Exception as e:
133
+ logger.warning(
134
+ "baseline_check_failed", error=str(e), severity="operational"
135
+ )
136
+ return False
137
+
138
+ def ensure_baseline(
139
+ self,
140
+ strategy: BaselineStrategy | None = None,
141
+ interactive: bool = True,
142
+ ) -> bool:
143
+ """Ensure a baseline exists, creating one if necessary.
144
+
145
+ For first sync, requires explicit baseline strategy selection.
146
+ Strategies:
147
+ - LOCAL: Use local code as baseline
148
+ - REMOTE: Use remote as baseline
149
+ - INTERACTIVE: Choose per-issue
150
+
151
+ Args:
152
+ strategy: Override strategy (LOCAL, REMOTE, or INTERACTIVE)
153
+ interactive: If False, raises error instead of prompting
154
+
155
+ Returns:
156
+ True if baseline exists or was created successfully
157
+ False if baseline creation failed
158
+
159
+ Raises:
160
+ RuntimeError: If baseline missing and interactive=False
161
+ """
162
+ if self.has_baseline():
163
+ logger.info("baseline_already_exists")
164
+ return True
165
+
166
+ logger.info("baseline_required_first_sync")
167
+
168
+ # Get strategy if not provided
169
+ if strategy is None:
170
+ if not interactive:
171
+ raise RuntimeError(
172
+ "Baseline required but no strategy provided and interactive=False"
173
+ )
174
+ result = self.baseline_selector.select_baseline()
175
+ strategy = result.strategy
176
+ else:
177
+ logger.info("baseline_strategy_provided", strategy=strategy.value)
178
+
179
+ # Create baseline based on strategy
180
+ try:
181
+ if strategy == BaselineStrategy.LOCAL:
182
+ return self._create_baseline_from_local()
183
+ elif strategy == BaselineStrategy.REMOTE:
184
+ return self._create_baseline_from_remote()
185
+ else:
186
+ # INTERACTIVE - would need per-issue prompts
187
+ logger.warning(
188
+ "interactive_baseline_not_fully_implemented_yet",
189
+ severity="operational",
190
+ )
191
+ return self._create_baseline_from_local()
192
+
193
+ except Exception as e:
194
+ logger.error(
195
+ "baseline_creation_failed", error=str(e), severity="system_error"
196
+ )
197
+ return False
198
+
199
+ def _create_baseline_from_local(self) -> bool:
200
+ """Create baseline from local state.
201
+
202
+ Returns:
203
+ True if successful
204
+ """
205
+ try:
206
+ logger.info("creating_baseline_from_local")
207
+ baseline = self._create_initial_baseline()
208
+ if baseline and baseline.base_issues:
209
+ # Save baseline to database for fast retrieval
210
+ baseline_dict = {
211
+ issue_id: {
212
+ "status": state.status,
213
+ "assignee": state.assignee,
214
+ "description": state.description or "",
215
+ "labels": state.labels or [],
216
+ }
217
+ for issue_id, state in baseline.base_issues.items()
218
+ }
219
+ self.core.db.save_sync_baseline(baseline_dict)
220
+
221
+ logger.info(
222
+ "baseline_created_from_local",
223
+ issue_count=len(baseline.base_issues),
224
+ )
225
+ return True
226
+ else:
227
+ logger.warning(
228
+ "baseline_creation_produced_empty_state", severity="operational"
229
+ )
230
+ return False
231
+ except Exception as e:
232
+ logger.error(
233
+ "baseline_creation_from_local_failed",
234
+ error=str(e),
235
+ severity="system_error",
236
+ )
237
+ return False
238
+
239
+ def _create_baseline_from_remote(self) -> bool:
240
+ """Create baseline from remote state.
241
+
242
+ Returns:
243
+ True if successful
244
+ """
245
+ try:
246
+ logger.info("creating_baseline_from_remote")
247
+
248
+ if not self._authenticate_for_baseline():
249
+ return False
250
+
251
+ remote_issues = self._fetch_remote_issues_for_baseline()
252
+ if not remote_issues:
253
+ return False
254
+
255
+ baseline = self._build_baseline_from_remote_issues(remote_issues)
256
+ if baseline.base_issues:
257
+ baseline_dict = {
258
+ issue_id: {
259
+ "status": state.status,
260
+ "assignee": state.assignee,
261
+ "description": state.headline or "",
262
+ "labels": state.labels or [],
263
+ }
264
+ for issue_id, state in baseline.base_issues.items()
265
+ }
266
+ self.core.db.save_sync_baseline(baseline_dict)
267
+ logger.info(
268
+ "baseline_created_from_remote",
269
+ issue_count=len(baseline.base_issues),
270
+ )
271
+ return True
272
+
273
+ logger.warning(
274
+ "baseline_creation_produced_empty_state",
275
+ severity="operational",
276
+ )
277
+ return False
278
+ except Exception as e:
279
+ logger.error(
280
+ "baseline_creation_from_remote_failed",
281
+ error=str(e),
282
+ severity="system_error",
283
+ )
284
+ return False
285
+
286
+ def _authenticate_for_baseline(self) -> bool:
287
+ auth_result = self.backend.authenticate()
288
+ if hasattr(auth_result, "is_err") and callable(auth_result.is_err):
289
+ if auth_result.is_err():
290
+ logger.error(
291
+ "backend_auth_failed_for_baseline",
292
+ error=str(auth_result.unwrap_err()),
293
+ severity="infrastructure",
294
+ )
295
+ return False
296
+ if not auth_result.unwrap():
297
+ logger.error(
298
+ "backend_auth_failed_for_baseline", severity="infrastructure"
299
+ )
300
+ return False
301
+ return True
302
+
303
+ if not auth_result:
304
+ logger.error("backend_auth_failed_for_baseline", severity="infrastructure")
305
+ return False
306
+ return True
307
+
308
+ def _fetch_remote_issues_for_baseline(self) -> dict[str, SyncIssue] | None:
309
+ remote_issues_result = self.backend.get_issues()
310
+ if hasattr(remote_issues_result, "is_err") and callable(
311
+ remote_issues_result.is_err
312
+ ):
313
+ if remote_issues_result.is_err():
314
+ logger.warning(
315
+ "backend_get_issues_failed",
316
+ error=str(remote_issues_result.unwrap_err()),
317
+ severity="operational",
318
+ )
319
+ return None
320
+ remote_issues = remote_issues_result.unwrap() or {}
321
+ elif isinstance(remote_issues_result, dict):
322
+ remote_issues = remote_issues_result
323
+ else:
324
+ logger.warning(
325
+ "backend_get_issues_unexpected_type",
326
+ result_type=type(remote_issues_result).__name__,
327
+ severity="operational",
328
+ )
329
+ return None
330
+
331
+ if not remote_issues:
332
+ logger.warning("no_remote_issues_for_baseline", severity="operational")
333
+ return None
334
+ return remote_issues
335
+
336
+ def _build_baseline_from_remote_issues(
337
+ self, remote_issues: dict[str, SyncIssue]
338
+ ) -> SyncState:
339
+ baseline = SyncState(last_sync_time=datetime.now(UTC))
340
+
341
+ from roadmap.core.services.sync.sync_state import IssueBaseState
342
+
343
+ for issue_id, remote_issue in remote_issues.items():
344
+ try:
345
+ baseline_state = IssueBaseState(
346
+ id=issue_id,
347
+ status=remote_issue.status or "todo",
348
+ title=remote_issue.title or "Untitled",
349
+ assignee=remote_issue.assignee,
350
+ headline=remote_issue.headline or "",
351
+ labels=remote_issue.labels or [],
352
+ updated_at=datetime.now(UTC),
353
+ )
354
+ baseline.base_issues[issue_id] = baseline_state
355
+ except Exception as e:
356
+ logger.warning(
357
+ "baseline_issue_conversion_failed",
358
+ issue_id=issue_id,
359
+ error=str(e),
360
+ severity="operational",
361
+ )
362
+ continue
363
+
364
+ return baseline
365
+
366
+ def _build_baseline_state_from_git_history(
367
+ self, last_synced: datetime | None
368
+ ) -> SyncState | None:
369
+ """Build baseline state by querying git history for each issue.
370
+
371
+ Instead of loading from a sync_base_state database table, we reconstruct
372
+ the baseline by reading each issue file as it existed at the last_synced
373
+ timestamp.
374
+
375
+ Args:
376
+ last_synced: Timestamp of last sync (from sync_metadata)
377
+
378
+ Returns:
379
+ SyncState with issues reconstructed from git history, or None
380
+ """
381
+ if not last_synced:
382
+ logger.debug(
383
+ "baseline_no_last_synced",
384
+ reason="first_sync_or_timestamp_missing",
385
+ )
386
+ return None
387
+
388
+ try:
389
+ logger.info(
390
+ "baseline_state_reconstruction_start",
391
+ last_synced=last_synced.isoformat(),
392
+ reason="building_from_git_history",
393
+ )
394
+
395
+ baseline = SyncState(
396
+ last_sync_time=last_synced,
397
+ )
398
+
399
+ # Get all current issue files (including archived)
400
+ try:
401
+ local_issues = self.core.issues.list_all_including_archived()
402
+ except Exception as e:
403
+ logger.warning(
404
+ "baseline_reconstruction_issue_list_failed",
405
+ error=str(e),
406
+ severity="operational",
407
+ )
408
+ return None
409
+
410
+ for issue in local_issues:
411
+ try:
412
+ # Find the issue file
413
+ issue_file = self._find_issue_file(issue.id)
414
+ if not issue_file:
415
+ logger.debug(
416
+ "baseline_issue_file_not_found",
417
+ issue_id=issue.id,
418
+ )
419
+ continue
420
+
421
+ # Retrieve local baseline from git history
422
+ local_baseline = self.baseline_retriever.get_local_baseline(
423
+ issue_file, last_synced
424
+ )
425
+
426
+ if local_baseline:
427
+ baseline.base_issues[issue.id] = local_baseline
428
+ logger.debug(
429
+ "baseline_issue_reconstructed_from_git",
430
+ issue_id=issue.id,
431
+ status=local_baseline.status,
432
+ )
433
+ else:
434
+ logger.debug(
435
+ "baseline_issue_not_in_git_history",
436
+ issue_id=issue.id,
437
+ reason="file_did_not_exist_at_timestamp",
438
+ )
439
+
440
+ except Exception as e:
441
+ logger.warning(
442
+ "baseline_reconstruction_issue_failed",
443
+ issue_id=issue.id,
444
+ error=str(e),
445
+ severity="operational",
446
+ )
447
+ continue
448
+
449
+ logger.info(
450
+ "baseline_state_reconstruction_complete",
451
+ reconstructed_count=len(baseline.base_issues),
452
+ reason="using_git_history",
453
+ )
454
+ return baseline
455
+
456
+ except Exception as e:
457
+ logger.error(
458
+ "baseline_state_reconstruction_error",
459
+ error=str(e),
460
+ error_type=type(e).__name__,
461
+ severity="system_error",
462
+ )
463
+ return None
464
+
465
+ def _build_baseline_state_from_sync_metadata(self) -> SyncState | None:
466
+ """Build baseline state from sync_metadata YAML frontmatter.
467
+
468
+ The remote baseline is stored in each issue's sync_metadata.remote_state,
469
+ which represents the last-synced state from the remote backend.
470
+
471
+ Args:
472
+ None (uses all local issues)
473
+
474
+ Returns:
475
+ SyncState with remote baselines from sync_metadata
476
+ """
477
+ try:
478
+ logger.debug(
479
+ "baseline_remote_retrieval_start",
480
+ reason="reading_sync_metadata_yaml",
481
+ )
482
+
483
+ # Get last_synced from first issue that has it (including archived)
484
+ last_synced_time = None
485
+ try:
486
+ local_issues = self.core.issues.list_all_including_archived()
487
+ for issue in local_issues:
488
+ issue_file = self._find_issue_file(issue.id)
489
+ if not issue_file:
490
+ continue
491
+
492
+ metadata = IssueParser.load_sync_metadata(issue_file)
493
+ if metadata and "last_synced" in metadata:
494
+ if isinstance(metadata["last_synced"], str):
495
+ last_synced_time = datetime.fromisoformat(
496
+ metadata["last_synced"]
497
+ )
498
+ else:
499
+ last_synced_time = metadata["last_synced"]
500
+ break
501
+ except Exception as e:
502
+ logger.warning(
503
+ "baseline_last_synced_extraction_failed",
504
+ error=str(e),
505
+ severity="operational",
506
+ )
507
+ return None
508
+
509
+ if not last_synced_time:
510
+ logger.debug(
511
+ "baseline_no_last_synced_in_metadata",
512
+ reason="no_sync_metadata_found",
513
+ )
514
+ return None
515
+
516
+ baseline = SyncState(
517
+ last_sync_time=last_synced_time,
518
+ )
519
+
520
+ # Build from sync_metadata remote_state
521
+ for issue in local_issues:
522
+ try:
523
+ issue_file = self._find_issue_file(issue.id)
524
+ if not issue_file:
525
+ continue
526
+
527
+ remote_baseline = self.baseline_retriever.get_remote_baseline(
528
+ issue_file
529
+ )
530
+
531
+ if remote_baseline:
532
+ baseline.base_issues[issue.id] = remote_baseline
533
+ logger.debug(
534
+ "baseline_remote_loaded_from_metadata",
535
+ issue_id=issue.id,
536
+ )
537
+
538
+ except Exception as e:
539
+ logger.warning(
540
+ "baseline_remote_retrieval_failed",
541
+ issue_id=issue.id,
542
+ error=str(e),
543
+ severity="operational",
544
+ )
545
+ continue
546
+
547
+ logger.info(
548
+ "baseline_remote_retrieval_complete",
549
+ count=len(baseline.base_issues),
550
+ reason="from_sync_metadata_yaml",
551
+ )
552
+ return baseline
553
+
554
+ except Exception as e:
555
+ logger.error(
556
+ "baseline_remote_retrieval_error",
557
+ error=str(e),
558
+ error_type=type(e).__name__,
559
+ severity="system_error",
560
+ )
561
+ return None
562
+
563
+ def _find_issue_file(self, issue_id: str) -> Path | None:
564
+ """Find the actual file path for an issue.
565
+
566
+ Issues can be in different directories based on milestone.
567
+
568
+ Args:
569
+ issue_id: Issue identifier
570
+
571
+ Returns:
572
+ Path to issue file, or None if not found
573
+ """
574
+ issues_dir = self.issues_dir
575
+ if not issues_dir.exists():
576
+ return None
577
+
578
+ # Search in milestone directories and backlog
579
+ search_dirs = [
580
+ issues_dir / "backlog",
581
+ issues_dir, # Root level
582
+ ]
583
+
584
+ # Also search milestone directories
585
+ for subdir in issues_dir.glob("*"):
586
+ if subdir.is_dir() and subdir.name not in ["backlog", "archive"]:
587
+ search_dirs.append(subdir)
588
+
589
+ for search_dir in search_dirs:
590
+ if not search_dir.exists():
591
+ continue
592
+ # Look for files matching issue_id prefix
593
+ for file in search_dir.glob(f"{issue_id}-*.md"):
594
+ return file
595
+
596
+ return None
597
+
598
+ def _create_initial_baseline(self) -> SyncState:
599
+ """Create initial baseline from current local state.
600
+
601
+ On first sync, we create a baseline from the current local state.
602
+ This becomes the reference point for future three-way merges.
603
+
604
+ Returns:
605
+ SyncState representing the current local state as the initial baseline
606
+ """
607
+ from datetime import datetime
608
+ from pathlib import Path
609
+
610
+ logger.info("creating_initial_baseline_from_local_state")
611
+
612
+ baseline = SyncState(
613
+ last_sync_time=datetime.now(UTC),
614
+ )
615
+
616
+ # Get all current local issues (including archived for baseline)
617
+ try:
618
+ local_issues = self.core.issues.list_all_including_archived()
619
+ for issue in local_issues:
620
+ try:
621
+ # Use issue.file_path which is set by the repository
622
+ # This handles both active and archived issues correctly
623
+ issue_file = Path(issue.file_path) if issue.file_path else None
624
+
625
+ if issue_file and issue_file.exists():
626
+ # For initial baseline, extract state directly from current file
627
+ # Don't use git history since we're establishing the first baseline
628
+ local_state = self.baseline_retriever.get_baseline_from_file(
629
+ issue_file
630
+ )
631
+ if local_state:
632
+ baseline.base_issues[issue.id] = local_state
633
+ except Exception as e:
634
+ logger.warning(
635
+ "initial_baseline_issue_reconstruction_failed",
636
+ issue_id=issue.id,
637
+ error=str(e),
638
+ severity="operational",
639
+ )
640
+ except Exception as e:
641
+ logger.error(
642
+ "initial_baseline_creation_failed",
643
+ error=str(e),
644
+ error_type=type(e).__name__,
645
+ severity="system_error",
646
+ )
647
+
648
+ logger.info(
649
+ "initial_baseline_created",
650
+ issue_count=len(baseline.base_issues),
651
+ )
652
+ return baseline
653
+
654
+ def get_baseline_state(self) -> SyncState | None:
655
+ """Get baseline state using database cache with git fallback.
656
+
657
+ Strategy:
658
+ 1. Try to load baseline from database (fast: ~10ms)
659
+ 2. If not in database, fall back to git history (slower: ~500ms)
660
+ 3. If git fails, load from YAML sync_metadata
661
+
662
+ Returns:
663
+ SyncState with local and remote baselines
664
+ """
665
+ try:
666
+ logger.debug("baseline_state_retrieval_start")
667
+
668
+ # PHASE 3: Try database baseline first (fast path)
669
+ db_baseline = self.core.db.get_sync_baseline()
670
+ if db_baseline:
671
+ logger.debug(
672
+ "baseline_state_loaded_from_database",
673
+ issue_count=len(db_baseline),
674
+ source="sync_base_state_table",
675
+ )
676
+
677
+ # Convert from database format to SyncState format
678
+ sync_state = SyncState(
679
+ last_sync_time=datetime.now(UTC),
680
+ )
681
+ for issue_id, data in db_baseline.items():
682
+ sync_state.base_issues[issue_id] = IssueBaseState(
683
+ id=issue_id,
684
+ status=data.get("status", "todo"),
685
+ title="", # Title not stored in baseline, will come from issue
686
+ assignee=data.get("assignee"),
687
+ description=data.get("description", ""),
688
+ labels=data.get("labels", []),
689
+ )
690
+
691
+ return sync_state
692
+
693
+ # Fallback: Try git history approach (original logic)
694
+ logger.debug("database_baseline_not_found_falling_back_to_git_history")
695
+
696
+ # Try to load last_synced timestamp from any issue's sync_metadata
697
+ remote_baseline = self._build_baseline_state_from_sync_metadata()
698
+
699
+ if remote_baseline:
700
+ # Now get local baseline from git history
701
+ local_baseline = self._build_baseline_state_from_git_history(
702
+ remote_baseline.last_sync_time
703
+ )
704
+
705
+ if local_baseline:
706
+ # Merge: use git local baseline, keep remote from YAML
707
+ merged = SyncState(
708
+ last_sync_time=remote_baseline.last_sync_time,
709
+ )
710
+
711
+ # Prefer git-reconstructed local baseline over YAML remote
712
+ # Remote is used for reference but git is source of truth
713
+ for issue_id, local_base in local_baseline.base_issues.items():
714
+ merged.base_issues[issue_id] = local_base
715
+
716
+ logger.info(
717
+ "baseline_state_loaded",
718
+ source="git_history_and_sync_metadata",
719
+ issue_count=len(merged.base_issues),
720
+ )
721
+ return merged
722
+ else:
723
+ # Only have remote baseline from YAML
724
+ logger.info(
725
+ "baseline_state_loaded",
726
+ source="sync_metadata_only",
727
+ issue_count=len(remote_baseline.base_issues),
728
+ )
729
+ return remote_baseline
730
+
731
+ # No baseline available (no database, git, or metadata)
732
+ logger.info(
733
+ "baseline_state_not_found",
734
+ reason="first_sync_or_no_metadata",
735
+ )
736
+ return None
737
+
738
+ except Exception as e:
739
+ logger.error(
740
+ "baseline_state_retrieval_error",
741
+ error=str(e),
742
+ error_type=type(e).__name__,
743
+ severity="system_error",
744
+ )
745
+ return None
746
+
747
+ def sync_all_issues(
748
+ self,
749
+ dry_run: bool = True,
750
+ force_local: bool = False,
751
+ force_remote: bool = False,
752
+ push_only: bool = False,
753
+ pull_only: bool = False,
754
+ ):
755
+ """Override sync_all_issues to use git-based baselines."""
756
+ try:
757
+ # Use git-based baseline retrieval instead of JSON file
758
+ logger.info("sync_starting_with_git_based_baselines")
759
+
760
+ git_baseline = self.get_baseline_state()
761
+ if git_baseline:
762
+ # Temporarily replace state_manager's loaded state
763
+ logger.info(
764
+ "using_git_based_baseline",
765
+ issue_count=len(git_baseline.base_issues),
766
+ )
767
+
768
+ # Call parent sync with git-based baseline
769
+ # We'll do this by temporarily patching the state_manager
770
+ original_load = self.state_manager.load_sync_state
771
+ self.state_manager.load_sync_state = lambda: git_baseline
772
+
773
+ try:
774
+ return super().sync_all_issues(
775
+ dry_run=dry_run,
776
+ force_local=force_local,
777
+ force_remote=force_remote,
778
+ push_only=push_only,
779
+ pull_only=pull_only,
780
+ )
781
+ finally:
782
+ self.state_manager.load_sync_state = original_load
783
+ else:
784
+ # No baseline - create initial baseline from current local state
785
+ logger.info("no_baseline_available_creating_initial_baseline")
786
+
787
+ initial_baseline = self._create_initial_baseline()
788
+
789
+ # Use the initial baseline for this sync
790
+ original_load = self.state_manager.load_sync_state
791
+ self.state_manager.load_sync_state = lambda: initial_baseline
792
+
793
+ try:
794
+ return super().sync_all_issues(
795
+ dry_run=dry_run,
796
+ force_local=force_local,
797
+ force_remote=force_remote,
798
+ push_only=push_only,
799
+ pull_only=pull_only,
800
+ )
801
+ finally:
802
+ self.state_manager.load_sync_state = original_load
803
+
804
+ except Exception as e:
805
+ logger.error(
806
+ "sync_error",
807
+ error=str(e),
808
+ error_type=type(e).__name__,
809
+ )
810
+ raise