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,296 @@
1
+ """Sync report data model and formatting."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import UTC, datetime
5
+ from typing import Any
6
+
7
+ from roadmap.common.console import get_console
8
+ from roadmap.core.domain.issue import Issue
9
+ from roadmap.core.services.sync.sync_state import IssueBaseState
10
+
11
+ console = get_console()
12
+
13
+
14
+ @dataclass
15
+ class IssueChange:
16
+ """Represents changes to a single issue during sync - three-way aware.
17
+
18
+ Tracks baseline, local, and remote states to provide complete context
19
+ for three-way merge analysis.
20
+ """
21
+
22
+ issue_id: str
23
+ title: str
24
+
25
+ # Three-way states (optional for backward compatibility)
26
+ baseline_state: IssueBaseState | None = None # State at last sync
27
+ local_state: Issue | None = None # Current local state (None if only in remote)
28
+ remote_state: dict[str, Any] | None = (
29
+ None # Current remote state (None if only in local)
30
+ )
31
+
32
+ # Analyzed changes (what changed from baseline)
33
+ local_changes: dict[str, Any] = field(default_factory=dict) # baseline → local
34
+ remote_changes: dict[str, Any] = field(default_factory=dict) # baseline → remote
35
+
36
+ # Conflict analysis
37
+ conflict_type: str = (
38
+ "no_change" # "both_changed", "local_only", "remote_only", "no_change"
39
+ )
40
+ has_conflict: bool = False
41
+ flagged_conflicts: dict[str, Any] = field(default_factory=dict)
42
+
43
+ # Legacy fields for backward compatibility
44
+ github_changes: dict[str, Any] = field(default_factory=dict)
45
+ last_sync_time: datetime | None = None
46
+
47
+ def get_conflict_description(self) -> str:
48
+ """Get human-readable conflict description including three-way context."""
49
+ if not self.has_conflict:
50
+ return ""
51
+
52
+ parts = []
53
+
54
+ if self.baseline_state:
55
+ parts.append(
56
+ f"Baseline: {self.baseline_state.status if hasattr(self.baseline_state, 'status') else 'unknown'}"
57
+ )
58
+
59
+ if self.local_changes:
60
+ local_desc = ", ".join(f"{k}: {v}" for k, v in self.local_changes.items())
61
+ parts.append(f"Local: {local_desc}")
62
+
63
+ # Support both new remote_changes and legacy github_changes
64
+ changes_to_show = self.remote_changes or self.github_changes
65
+ if changes_to_show:
66
+ remote_desc = ", ".join(f"{k}: {v}" for k, v in changes_to_show.items())
67
+ label = "Remote" if self.remote_changes else "GitHub"
68
+ parts.append(f"{label}: {remote_desc}")
69
+
70
+ return " → ".join(parts)
71
+
72
+ def get_change_description(self) -> str:
73
+ """Get human-readable change description."""
74
+ # Support both new and legacy fields
75
+ local_chgs = self.local_changes
76
+ remote_chgs = self.remote_changes or self.github_changes
77
+
78
+ if local_chgs and remote_chgs:
79
+ remote_label = "Remote" if self.remote_changes else "GitHub"
80
+ return f"Local: {local_chgs} | {remote_label}: {remote_chgs}"
81
+ elif local_chgs:
82
+ return f"Local: {local_chgs}"
83
+ elif remote_chgs:
84
+ remote_label = "Remote" if self.remote_changes else "GitHub"
85
+ return f"{remote_label}: {remote_chgs}"
86
+ return "No changes"
87
+
88
+ def is_three_way_conflict(self) -> bool:
89
+ """Check if both local and remote changed from baseline."""
90
+ return bool(self.local_changes and self.remote_changes)
91
+
92
+ def is_local_only_change(self) -> bool:
93
+ """Check if only local changed from baseline."""
94
+ return bool(self.local_changes and not self.remote_changes)
95
+
96
+ def is_remote_only_change(self) -> bool:
97
+ """Check if only remote changed from baseline."""
98
+ return bool(self.remote_changes and not self.local_changes)
99
+
100
+
101
+ @dataclass
102
+ class SyncReport:
103
+ """Complete sync operation report."""
104
+
105
+ # Operation context
106
+ operation_id: str | None = None
107
+
108
+ # Local items
109
+ total_issues: int = 0
110
+ active_issues: int = 0
111
+ archived_issues: int = 0
112
+ total_milestones: int = 0
113
+ active_milestones: int = 0
114
+ archived_milestones: int = 0
115
+
116
+ # Remote items
117
+ remote_total_issues: int = 0
118
+ remote_open_issues: int = 0
119
+ remote_closed_issues: int = 0
120
+ remote_total_milestones: int = 0
121
+
122
+ # Sync analysis statistics (three-way merge results)
123
+ issues_up_to_date: int = 0 # No changes in baseline, local, or remote
124
+ issues_needs_push: int = 0 # Local changes that need to be pushed
125
+ issues_needs_pull: int = 0 # Remote changes that need to be pulled
126
+ conflicts_detected: int = 0 # Issues with changes in both directions
127
+
128
+ # Sync application statistics (after applying changes)
129
+ issues_pushed: int = 0 # Issues successfully pushed to remote
130
+ issues_pulled: int = 0 # Issues successfully pulled from remote
131
+
132
+ # Duplicate detection and resolution statistics
133
+ duplicates_detected: int = 0 # Total duplicates found in local+remote issues
134
+ duplicates_auto_resolved: int = 0 # Duplicates automatically resolved (merged)
135
+ issues_deleted: int = 0 # Issues hard deleted (high-confidence ID collisions)
136
+ issues_archived: int = 0 # Issues soft archived (fuzzy matches, kept for history)
137
+
138
+ changes: list[IssueChange] = field(default_factory=list)
139
+ errors: dict[str, str] = field(default_factory=dict) # Issue ID -> error message
140
+ error_buckets: dict[str, int] = field(default_factory=dict)
141
+ baseline_update_failed: bool = False
142
+ timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
143
+ error: str | None = None
144
+ metrics: Any | None = None # SyncMetrics object if available
145
+
146
+ def display_brief(self) -> None:
147
+ """Display brief sync summary with local and remote item breakdown tables."""
148
+ if self.error:
149
+ console.print(f"❌ Sync failed: {self.error}", style="bold red")
150
+ return
151
+
152
+ if self.operation_id:
153
+ console.print(f"Operation ID: {self.operation_id}", style="dim")
154
+
155
+ console.print("\n📊 Sync Report", style="bold cyan")
156
+
157
+ # Create items tables
158
+ from rich.table import Table
159
+
160
+ # Local table - ACTIVE || ARCHIVED || TOTAL format with colors
161
+ local_table = Table(
162
+ title="Local Items",
163
+ show_header=True,
164
+ header_style="bold cyan",
165
+ border_style="cyan",
166
+ )
167
+ local_table.add_column("Type", style="cyan")
168
+ local_table.add_column("Active", justify="right", style="green")
169
+ local_table.add_column("Archived", justify="right", style="yellow")
170
+ local_table.add_column("Total", justify="right", style="bold cyan")
171
+
172
+ local_table.add_row(
173
+ "Issues",
174
+ str(self.active_issues),
175
+ str(self.archived_issues),
176
+ str(self.total_issues),
177
+ )
178
+ local_table.add_row(
179
+ "Milestones",
180
+ str(self.active_milestones),
181
+ str(self.archived_milestones),
182
+ str(self.total_milestones),
183
+ )
184
+
185
+ # Remote table with colors
186
+ remote_table = Table(
187
+ title="Remote Items",
188
+ show_header=True,
189
+ header_style="bold yellow",
190
+ border_style="yellow",
191
+ )
192
+ remote_table.add_column("Type", style="yellow")
193
+ remote_table.add_column("Open", justify="right", style="green")
194
+ remote_table.add_column("Closed", justify="right", style="red")
195
+ remote_table.add_column("Total", justify="right", style="bold yellow")
196
+
197
+ remote_table.add_row(
198
+ "Issues",
199
+ str(self.remote_open_issues),
200
+ str(self.remote_closed_issues),
201
+ str(self.remote_total_issues),
202
+ )
203
+ remote_table.add_row("Milestones", "—", "—", str(self.remote_total_milestones))
204
+
205
+ # Display tables side by side using renderables
206
+ from rich.columns import Columns
207
+
208
+ columns = Columns([local_table, remote_table], equal=True, expand=True)
209
+ console.print(columns)
210
+
211
+ # Sync analysis summary
212
+ console.print("\n📈 Sync Analysis", style="bold cyan")
213
+ console.print(f" ✓ Up-to-date: {self.issues_up_to_date}", style="green")
214
+ console.print(f" 📤 Needs Push: {self.issues_needs_push}", style="blue")
215
+ console.print(f" 📥 Needs Pull: {self.issues_needs_pull}", style="magenta")
216
+
217
+ if self.conflicts_detected > 0:
218
+ console.print(
219
+ f" ⚠️ Potential Conflicts: {self.conflicts_detected}",
220
+ style="bold red",
221
+ )
222
+ console.print(
223
+ " Use --force-local or --force-github to resolve",
224
+ style="dim red",
225
+ )
226
+ else:
227
+ console.print(
228
+ f" ✓ Potential Conflicts: {self.conflicts_detected}", style="green"
229
+ )
230
+
231
+ # Applied changes summary (if not dry-run)
232
+ if self.issues_pushed > 0 or self.issues_pulled > 0:
233
+ console.print("\n✨ Applied Changes", style="bold green")
234
+ if self.issues_pushed > 0:
235
+ console.print(f" Pushed: {self.issues_pushed}", style="green")
236
+ if self.issues_pulled > 0:
237
+ console.print(f" Pulled: {self.issues_pulled}", style="green")
238
+
239
+ if self.error_buckets:
240
+ bucket_summary = ", ".join(
241
+ f"{key}={value}" for key, value in sorted(self.error_buckets.items())
242
+ )
243
+ console.print(f" Errors: {bucket_summary}", style="yellow")
244
+
245
+ if self.baseline_update_failed:
246
+ console.print(
247
+ "⚠️ Baseline update failed (next sync may re-detect changes)",
248
+ style="yellow",
249
+ )
250
+
251
+ def display_verbose(self) -> None:
252
+ """Display verbose output: show brief summary plus detailed issue IDs being synced."""
253
+ if self.error:
254
+ console.print(f"❌ Sync failed: {self.error}", style="bold red")
255
+ return
256
+
257
+ # First, show the brief output (tables and summary)
258
+ self.display_brief()
259
+
260
+ # Then add detailed lists of issue IDs being synced
261
+ if not self.changes:
262
+ return
263
+
264
+ console.print("\n📋 Detailed Issue Changes", style="bold cyan")
265
+
266
+ # Categorize changes by type
267
+ needs_push = [c for c in self.changes if c.local_changes]
268
+ needs_pull = [c for c in self.changes if c.remote_changes]
269
+ has_conflicts = [c for c in self.changes if c.has_conflict]
270
+
271
+ if has_conflicts:
272
+ console.print("\n 🔴 Conflicts:", style="bold red")
273
+ conflict_ids = [f"{c.issue_id}" for c in has_conflicts]
274
+ console.print(f" {', '.join(conflict_ids)}")
275
+
276
+ if needs_push:
277
+ console.print("\n 📤 Pushing:", style="bold blue")
278
+ push_ids = [f"{c.issue_id}" for c in needs_push]
279
+ console.print(f" {', '.join(push_ids)}")
280
+
281
+ if needs_pull:
282
+ console.print("\n 📥 Pulling:", style="bold magenta")
283
+ pull_ids = [f"{c.issue_id}" for c in needs_pull]
284
+ console.print(f" {', '.join(pull_ids)}")
285
+
286
+ def has_conflicts(self) -> bool:
287
+ """Check if there are any conflicts."""
288
+ return self.conflicts_detected > 0
289
+
290
+ def has_changes(self) -> bool:
291
+ """Check if there are any changes."""
292
+ return (
293
+ self.issues_needs_push > 0
294
+ or self.issues_needs_pull > 0
295
+ or self.conflicts_detected > 0
296
+ )
@@ -0,0 +1,173 @@
1
+ """Sync state models for tracking local and remote state."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import UTC, datetime
5
+ from typing import Any
6
+
7
+
8
+ @dataclass
9
+ class IssueBaseState:
10
+ """Base state of an issue from sync perspective."""
11
+
12
+ id: str
13
+ status: str
14
+ assignee: str | None = None
15
+ labels: list[str] = field(default_factory=list)
16
+ description: str = ""
17
+ title: str = ""
18
+ headline: str = ""
19
+ content: str = ""
20
+ priority: int = 0
21
+ blocked_by: list[str] = field(default_factory=list)
22
+ blocks: list[str] = field(default_factory=list)
23
+ created_at: datetime | None = None
24
+ updated_at: datetime | None = None
25
+ archived: bool = False
26
+ custom_fields: dict[str, Any] = field(default_factory=dict)
27
+
28
+ def to_dict(self) -> dict[str, Any]:
29
+ """Convert to dictionary for comparison."""
30
+ return {
31
+ "id": self.id,
32
+ "status": self.status,
33
+ "assignee": self.assignee,
34
+ "labels": self.labels,
35
+ "description": self.description,
36
+ "title": self.title,
37
+ "headline": self.headline,
38
+ "content": self.content,
39
+ "priority": self.priority,
40
+ "blocked_by": self.blocked_by,
41
+ "blocks": self.blocks,
42
+ "created_at": self.created_at,
43
+ "updated_at": self.updated_at,
44
+ "archived": self.archived,
45
+ "custom_fields": self.custom_fields,
46
+ }
47
+
48
+ @classmethod
49
+ def from_dict(cls, data: dict[str, Any]) -> "IssueBaseState":
50
+ """Create from dictionary."""
51
+ return cls(**data)
52
+
53
+
54
+ @dataclass
55
+ class SyncState:
56
+ """Current sync state of the system."""
57
+
58
+ local_issues: dict[str, IssueBaseState] = field(default_factory=dict)
59
+ remote_issues: dict[str, IssueBaseState] = field(default_factory=dict)
60
+ base_issues: dict[str, IssueBaseState] = field(default_factory=dict)
61
+ last_sync_time: datetime | None = None
62
+ sync_in_progress: bool = False
63
+ local_deleted_ids: set[str] = field(default_factory=set)
64
+ remote_deleted_ids: set[str] = field(default_factory=set)
65
+
66
+ def get_issue_dict(self, source: str) -> dict[str, dict[str, Any]]:
67
+ """Get issues as dictionary of dicts for merging."""
68
+ if source == "local":
69
+ issues = self.local_issues
70
+ elif source == "remote":
71
+ issues = self.remote_issues
72
+ elif source == "base":
73
+ issues = self.base_issues
74
+ else:
75
+ raise ValueError(f"Unknown source: {source}")
76
+
77
+ return {issue_id: state.to_dict() for issue_id, state in issues.items()}
78
+
79
+ def add_issue(
80
+ self,
81
+ source: str,
82
+ issue: IssueBaseState,
83
+ ) -> None:
84
+ """Add or update an issue in the specified source."""
85
+ if source == "local":
86
+ self.local_issues[issue.id] = issue
87
+ elif source == "remote":
88
+ self.remote_issues[issue.id] = issue
89
+ elif source == "base":
90
+ self.base_issues[issue.id] = issue
91
+ else:
92
+ raise ValueError(f"Unknown source: {source}")
93
+
94
+ def get_issue(self, source: str, issue_id: str) -> IssueBaseState | None:
95
+ """Get a specific issue from a source."""
96
+ if source == "local":
97
+ return self.local_issues.get(issue_id)
98
+ elif source == "remote":
99
+ return self.remote_issues.get(issue_id)
100
+ elif source == "base":
101
+ return self.base_issues.get(issue_id)
102
+ else:
103
+ raise ValueError(f"Unknown source: {source}")
104
+
105
+ def mark_deleted(self, source: str, issue_id: str) -> None:
106
+ """Mark an issue as deleted in the specified source."""
107
+ if source == "local":
108
+ self.local_deleted_ids.add(issue_id)
109
+ if issue_id in self.local_issues:
110
+ del self.local_issues[issue_id]
111
+ elif source == "remote":
112
+ self.remote_deleted_ids.add(issue_id)
113
+ if issue_id in self.remote_issues:
114
+ del self.remote_issues[issue_id]
115
+ else:
116
+ raise ValueError(f"Unknown source: {source}")
117
+
118
+ def mark_synced(self) -> None:
119
+ """Mark sync as complete."""
120
+ self.sync_in_progress = False
121
+ self.last_sync_time = datetime.now(UTC)
122
+
123
+ def to_dict(self) -> dict[str, Any]:
124
+ """Convert to dictionary for serialization."""
125
+ return {
126
+ "local_issues": {
127
+ issue_id: state.to_dict()
128
+ for issue_id, state in self.local_issues.items()
129
+ },
130
+ "remote_issues": {
131
+ issue_id: state.to_dict()
132
+ for issue_id, state in self.remote_issues.items()
133
+ },
134
+ "base_issues": {
135
+ issue_id: state.to_dict()
136
+ for issue_id, state in self.base_issues.items()
137
+ },
138
+ "last_sync_time": self.last_sync_time.isoformat()
139
+ if self.last_sync_time
140
+ else None,
141
+ "sync_in_progress": self.sync_in_progress,
142
+ "local_deleted_ids": list(self.local_deleted_ids),
143
+ "remote_deleted_ids": list(self.remote_deleted_ids),
144
+ }
145
+
146
+ @classmethod
147
+ def from_dict(cls, data: dict[str, Any]) -> "SyncState":
148
+ """Create from dictionary."""
149
+ local_issues = {
150
+ issue_id: IssueBaseState.from_dict(state)
151
+ for issue_id, state in data.get("local_issues", {}).items()
152
+ }
153
+ remote_issues = {
154
+ issue_id: IssueBaseState.from_dict(state)
155
+ for issue_id, state in data.get("remote_issues", {}).items()
156
+ }
157
+ base_issues = {
158
+ issue_id: IssueBaseState.from_dict(state)
159
+ for issue_id, state in data.get("base_issues", {}).items()
160
+ }
161
+ last_sync_time = None
162
+ if data.get("last_sync_time"):
163
+ last_sync_time = datetime.fromisoformat(data["last_sync_time"])
164
+
165
+ return cls(
166
+ local_issues=local_issues,
167
+ remote_issues=remote_issues,
168
+ base_issues=base_issues,
169
+ last_sync_time=last_sync_time,
170
+ sync_in_progress=data.get("sync_in_progress", False),
171
+ local_deleted_ids=set(data.get("local_deleted_ids", [])),
172
+ remote_deleted_ids=set(data.get("remote_deleted_ids", [])),
173
+ )