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,361 @@
1
+ """Sync metadata service for tracking issue sync history and statistics."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import UTC, datetime
5
+ from typing import Any
6
+
7
+ import structlog
8
+
9
+ from roadmap.core.domain import Issue
10
+
11
+ logger = structlog.get_logger()
12
+
13
+
14
+ @dataclass
15
+ class SyncRecord:
16
+ """Record of a single sync operation for an issue."""
17
+
18
+ sync_timestamp: str # ISO format
19
+ success: bool
20
+ local_changes: dict[str, Any] | None = None
21
+ github_changes: dict[str, Any] | None = None
22
+ conflict_resolution: str | None = None # "local", "github", or None
23
+ error_message: str | None = None
24
+ milestone_dependencies: list[str] = field(
25
+ default_factory=list
26
+ ) # Milestone IDs this sync depended on
27
+ dependency_resolution_time: float | None = (
28
+ None # Time spent resolving dependencies in seconds
29
+ )
30
+
31
+
32
+ @dataclass
33
+ class SyncMetadata:
34
+ """Metadata tracking for issue synchronization."""
35
+
36
+ issue_id: str
37
+ github_issue_id: int
38
+ last_sync_time: str | None = None
39
+ sync_count: int = 0
40
+ successful_syncs: int = 0
41
+ last_sync_status: str = "never" # "success", "conflict", "error", "never"
42
+ sync_history: list[SyncRecord] = field(default_factory=list)
43
+ milestone_sync_count: int = 0 # Number of syncs involving milestones
44
+ last_milestone_resolution_time: float | None = (
45
+ None # Last dependency resolution time
46
+ )
47
+ circular_dependencies_detected: int = 0 # Count of circular dependency detections
48
+
49
+ def add_sync_record(self, record: SyncRecord) -> None:
50
+ """Add a sync record to history."""
51
+ self.sync_history.append(record)
52
+ self.sync_count += 1
53
+ self.last_sync_time = record.sync_timestamp
54
+
55
+ if record.success:
56
+ self.successful_syncs += 1
57
+ self.last_sync_status = "success"
58
+ elif record.conflict_resolution:
59
+ self.last_sync_status = "conflict"
60
+ else:
61
+ self.last_sync_status = "error"
62
+
63
+ # Track milestone-specific metrics
64
+ if record.milestone_dependencies:
65
+ self.milestone_sync_count += 1
66
+ if record.dependency_resolution_time is not None:
67
+ self.last_milestone_resolution_time = record.dependency_resolution_time
68
+
69
+ def get_success_rate(self) -> float:
70
+ """Calculate sync success rate."""
71
+ if self.sync_count == 0:
72
+ return 0.0
73
+ return (self.successful_syncs / self.sync_count) * 100
74
+
75
+ def to_dict(self) -> dict[str, Any]:
76
+ """Convert to dictionary for storage."""
77
+ return {
78
+ "issue_id": self.issue_id,
79
+ "github_issue_id": self.github_issue_id,
80
+ "last_sync_time": self.last_sync_time,
81
+ "sync_count": self.sync_count,
82
+ "successful_syncs": self.successful_syncs,
83
+ "last_sync_status": self.last_sync_status,
84
+ "milestone_sync_count": self.milestone_sync_count,
85
+ "last_milestone_resolution_time": self.last_milestone_resolution_time,
86
+ "circular_dependencies_detected": self.circular_dependencies_detected,
87
+ "sync_history": [
88
+ {
89
+ "sync_timestamp": r.sync_timestamp,
90
+ "success": r.success,
91
+ "local_changes": r.local_changes,
92
+ "github_changes": r.github_changes,
93
+ "conflict_resolution": r.conflict_resolution,
94
+ "error_message": r.error_message,
95
+ "milestone_dependencies": r.milestone_dependencies,
96
+ "dependency_resolution_time": r.dependency_resolution_time,
97
+ }
98
+ for r in self.sync_history
99
+ ],
100
+ }
101
+
102
+ @classmethod
103
+ def from_dict(cls, data: dict[str, Any]) -> "SyncMetadata":
104
+ """Create from dictionary (loaded from storage)."""
105
+ history = [
106
+ SyncRecord(
107
+ sync_timestamp=r["sync_timestamp"],
108
+ success=r["success"],
109
+ local_changes=r.get("local_changes"),
110
+ github_changes=r.get("github_changes"),
111
+ conflict_resolution=r.get("conflict_resolution"),
112
+ error_message=r.get("error_message"),
113
+ milestone_dependencies=r.get("milestone_dependencies", []),
114
+ dependency_resolution_time=r.get("dependency_resolution_time"),
115
+ )
116
+ for r in data.get("sync_history", [])
117
+ ]
118
+ return cls(
119
+ issue_id=data["issue_id"],
120
+ github_issue_id=data["github_issue_id"],
121
+ last_sync_time=data.get("last_sync_time"),
122
+ sync_count=data.get("sync_count", 0),
123
+ successful_syncs=data.get("successful_syncs", 0),
124
+ last_sync_status=data.get("last_sync_status", "never"),
125
+ milestone_sync_count=data.get("milestone_sync_count", 0),
126
+ last_milestone_resolution_time=data.get("last_milestone_resolution_time"),
127
+ circular_dependencies_detected=data.get(
128
+ "circular_dependencies_detected", 0
129
+ ),
130
+ sync_history=history,
131
+ )
132
+
133
+
134
+ class SyncMetadataService:
135
+ """Service for managing sync metadata persistence and queries."""
136
+
137
+ def __init__(self, core):
138
+ """Initialize metadata service.
139
+
140
+ Args:
141
+ core: RoadmapCore instance
142
+ """
143
+ self.core = core
144
+ # In-memory cache of metadata (keyed by issue_id)
145
+ self._cache: dict[str, SyncMetadata] = {}
146
+
147
+ def get_metadata(self, issue: Issue) -> SyncMetadata:
148
+ """Get sync metadata for an issue.
149
+
150
+ Args:
151
+ issue: Issue to get metadata for
152
+
153
+ Returns:
154
+ SyncMetadata for the issue
155
+ """
156
+ if issue.id in self._cache:
157
+ return self._cache[issue.id]
158
+
159
+ # Try to load from issue's github_sync_metadata field
160
+ if hasattr(issue, "github_sync_metadata") and issue.github_sync_metadata:
161
+ try:
162
+ metadata = SyncMetadata.from_dict(issue.github_sync_metadata)
163
+ self._cache[issue.id] = metadata
164
+ return metadata
165
+ except Exception as e:
166
+ logger.debug(
167
+ "sync_metadata_deserialization_failed",
168
+ operation="load_from_cache",
169
+ issue_id=issue.id,
170
+ error=str(e),
171
+ action="Creating new metadata",
172
+ )
173
+
174
+ # Create new metadata
175
+ github_id = getattr(issue, "github_issue", None)
176
+ metadata = SyncMetadata(
177
+ issue_id=issue.id,
178
+ github_issue_id=github_id or 0,
179
+ )
180
+ self._cache[issue.id] = metadata
181
+ return metadata
182
+
183
+ def record_sync(
184
+ self,
185
+ issue: Issue,
186
+ success: bool,
187
+ local_changes: dict[str, Any] | None = None,
188
+ github_changes: dict[str, Any] | None = None,
189
+ conflict_resolution: str | None = None,
190
+ error_message: str | None = None,
191
+ ) -> None:
192
+ """Record a sync operation for an issue.
193
+
194
+ Args:
195
+ issue: Issue that was synced
196
+ success: Whether sync succeeded
197
+ local_changes: Local changes detected
198
+ github_changes: GitHub changes detected
199
+ conflict_resolution: How conflicts were resolved
200
+ error_message: Error message if sync failed
201
+ """
202
+ metadata = self.get_metadata(issue)
203
+
204
+ record = SyncRecord(
205
+ sync_timestamp=datetime.now(UTC).isoformat(),
206
+ success=success,
207
+ local_changes=local_changes,
208
+ github_changes=github_changes,
209
+ conflict_resolution=conflict_resolution,
210
+ error_message=error_message,
211
+ )
212
+ metadata.add_sync_record(record)
213
+
214
+ # Persist to issue
215
+ if not hasattr(issue, "github_sync_metadata"):
216
+ issue.github_sync_metadata = {}
217
+ issue.github_sync_metadata = metadata.to_dict()
218
+
219
+ def get_sync_history(self, issue: Issue, limit: int = 10) -> list[SyncRecord]:
220
+ """Get sync history for an issue.
221
+
222
+ Args:
223
+ issue: Issue to get history for
224
+ limit: Maximum number of records to return
225
+
226
+ Returns:
227
+ List of sync records (most recent first)
228
+ """
229
+ metadata = self.get_metadata(issue)
230
+ return list(reversed(metadata.sync_history))[:limit]
231
+
232
+ def get_statistics(self, issues: list[Issue]) -> dict[str, Any]:
233
+ """Get aggregate sync statistics across issues.
234
+
235
+ Args:
236
+ issues: List of issues to analyze
237
+
238
+ Returns:
239
+ Dictionary with statistics
240
+ """
241
+ total_issues = len(issues)
242
+ total_syncs = 0
243
+ successful_syncs = 0
244
+ never_synced = 0
245
+ total_conflicts = 0
246
+
247
+ for issue in issues:
248
+ metadata = self.get_metadata(issue)
249
+ total_syncs += metadata.sync_count
250
+ successful_syncs += metadata.successful_syncs
251
+
252
+ if metadata.sync_count == 0:
253
+ never_synced += 1
254
+
255
+ total_conflicts += sum(
256
+ 1 for record in metadata.sync_history if record.conflict_resolution
257
+ )
258
+
259
+ return {
260
+ "total_issues": total_issues,
261
+ "never_synced": never_synced,
262
+ "total_sync_attempts": total_syncs,
263
+ "successful_syncs": successful_syncs,
264
+ "total_conflicts": total_conflicts,
265
+ "success_rate": (
266
+ (successful_syncs / total_syncs * 100) if total_syncs > 0 else 0
267
+ ),
268
+ "conflict_rate": (
269
+ (total_conflicts / total_syncs * 100) if total_syncs > 0 else 0
270
+ ),
271
+ }
272
+
273
+ def store_sync_metrics(
274
+ self, core, operation_id: str, metrics: dict[str, Any]
275
+ ) -> None:
276
+ """Store sync metrics to database.
277
+
278
+ Stores metrics as JSON in sync_metrics table for historical tracking
279
+ and analysis.
280
+
281
+ Args:
282
+ core: RoadmapCore instance
283
+ operation_id: ID of the sync operation
284
+ metrics: SyncMetrics.to_dict() result with all metrics
285
+ """
286
+ try:
287
+ # Store to first issue's metadata as a global sync record
288
+ issues = []
289
+ if hasattr(core, "issues"):
290
+ issues = core.issues.list_all_including_archived() or []
291
+ elif hasattr(core, "issue_service"):
292
+ issues = core.issue_service.list_all_including_archived() or []
293
+ if issues:
294
+ first_issue = issues[0]
295
+ if not first_issue.github_sync_metadata:
296
+ first_issue.github_sync_metadata = {}
297
+
298
+ # Store metrics under special _sync_metrics key
299
+ first_issue.github_sync_metadata["_sync_metrics"] = {
300
+ "operation_id": operation_id,
301
+ "timestamp": datetime.now(UTC).isoformat(),
302
+ "metrics": metrics,
303
+ }
304
+
305
+ # Persist via repository to ensure metadata is written
306
+ from roadmap.adapters.sync.services import IssuePersistenceService
307
+
308
+ saved = IssuePersistenceService.save_issue(first_issue, core)
309
+ if not saved:
310
+ logger.warning(
311
+ "sync_metrics_persist_failed",
312
+ operation_id=operation_id,
313
+ issue_id=first_issue.id,
314
+ severity="operational",
315
+ )
316
+ return
317
+
318
+ logger.info(
319
+ "sync_metrics_stored",
320
+ operation_id=operation_id,
321
+ timestamp=first_issue.github_sync_metadata["_sync_metrics"][
322
+ "timestamp"
323
+ ],
324
+ )
325
+ except Exception as e:
326
+ logger.error(
327
+ "sync_metrics_storage_failed",
328
+ operation_id=operation_id,
329
+ error=str(e),
330
+ severity="operational",
331
+ )
332
+
333
+ def get_last_sync_metrics(self, core) -> dict[str, Any] | None:
334
+ """Retrieve last sync metrics from database.
335
+
336
+ Returns:
337
+ Dictionary with metrics or None if no metrics stored
338
+ """
339
+ try:
340
+ issues = []
341
+ if hasattr(core, "issues"):
342
+ issues = core.issues.list_all_including_archived() or []
343
+ elif hasattr(core, "issue_service"):
344
+ issues = core.issue_service.list_all_including_archived() or []
345
+ if issues:
346
+ first_issue = issues[0]
347
+ if (
348
+ first_issue.github_sync_metadata
349
+ and "_sync_metrics" in first_issue.github_sync_metadata
350
+ ):
351
+ return first_issue.github_sync_metadata["_sync_metrics"].get(
352
+ "metrics"
353
+ )
354
+ return None
355
+ except Exception as e:
356
+ logger.error(
357
+ "sync_metrics_retrieval_failed",
358
+ error=str(e),
359
+ severity="operational",
360
+ )
361
+ return None
@@ -0,0 +1,171 @@
1
+ """Lightweight Action and SyncPlan models for incremental sync refactor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+
9
+ @dataclass
10
+ class Action:
11
+ """Base action representing a single mutating operation to be applied by the Executor.
12
+
13
+ This class is intentionally simple: it is a serializable carrier of the
14
+ operation type and any payload necessary to perform it. Executors will
15
+ implement the actual semantics of applying each action type.
16
+ """
17
+
18
+ action_type: str
19
+ payload: dict[str, Any] = field(default_factory=dict)
20
+
21
+ def describe(self) -> str:
22
+ """Human-readable description useful for dry-run previews."""
23
+ # Keep concise and deterministic for testing
24
+ payload_preview = {k: self.payload.get(k) for k in sorted(self.payload.keys())}
25
+ return f"{self.action_type}: {payload_preview}"
26
+
27
+
28
+ @dataclass
29
+ class PushAction(Action):
30
+ """Action to push a local issue to a remote backend."""
31
+
32
+ def __init__(self, issue_id: str, issue_payload: Any = None):
33
+ """Initialize PushAction.
34
+
35
+ Args:
36
+ issue_id: ID of the issue to push.
37
+ issue_payload: Payload data for the issue.
38
+ """
39
+ super().__init__(
40
+ action_type="push",
41
+ payload={"issue_id": issue_id, "issue": issue_payload or {}},
42
+ )
43
+
44
+
45
+ @dataclass
46
+ class PullAction(Action):
47
+ """Action to pull remote changes for a local issue."""
48
+
49
+ def __init__(self, issue_id: str, remote_payload: Any = None):
50
+ """Initialize PullAction.
51
+
52
+ Args:
53
+ issue_id: ID of the issue to pull.
54
+ remote_payload: Remote payload data.
55
+ """
56
+ super().__init__(
57
+ action_type="pull",
58
+ payload={"issue_id": issue_id, "remote": remote_payload or {}},
59
+ )
60
+
61
+
62
+ @dataclass
63
+ class CreateLocalAction(Action):
64
+ """Action to create a local issue from a remote entity."""
65
+
66
+ def __init__(self, remote_id: str, remote_payload: Any = None):
67
+ """Initialize CreateLocalAction.
68
+
69
+ Args:
70
+ remote_id: Remote ID of the issue.
71
+ remote_payload: Remote payload data.
72
+ """
73
+ super().__init__(
74
+ action_type="create_local",
75
+ payload={"remote_id": remote_id, "remote": remote_payload or {}},
76
+ )
77
+
78
+
79
+ @dataclass
80
+ class LinkAction(Action):
81
+ """Action to link a local issue with a remote issue."""
82
+
83
+ def __init__(self, issue_id: str, backend_name: str, remote_id: str):
84
+ """Initialize LinkAction.
85
+
86
+ Args:
87
+ issue_id: Local issue ID.
88
+ backend_name: Name of the backend.
89
+ remote_id: Remote ID to link to.
90
+ """
91
+ super().__init__(
92
+ action_type="link",
93
+ payload={
94
+ "issue_id": issue_id,
95
+ "backend": backend_name,
96
+ "remote_id": remote_id,
97
+ },
98
+ )
99
+
100
+
101
+ @dataclass
102
+ class UpdateBaselineAction(Action):
103
+ """Action to update the baseline snapshot."""
104
+
105
+ def __init__(self, baseline_snapshot: dict[str, Any]):
106
+ """Initialize UpdateBaselineAction.
107
+
108
+ Args:
109
+ baseline_snapshot: Baseline snapshot to update.
110
+ """
111
+ super().__init__(
112
+ action_type="update_baseline", payload={"baseline": baseline_snapshot}
113
+ )
114
+
115
+
116
+ @dataclass
117
+ class ResolveConflictAction(Action):
118
+ """Action to resolve a sync conflict for an issue."""
119
+
120
+ def __init__(self, issue_id: str, resolution: dict[str, Any]):
121
+ """Initialize ResolveConflictAction.
122
+
123
+ Args:
124
+ issue_id: ID of the issue with conflict.
125
+ resolution: Resolution data for the conflict.
126
+ """
127
+ super().__init__(
128
+ action_type="resolve_conflict",
129
+ payload={"issue_id": issue_id, "resolution": resolution},
130
+ )
131
+
132
+
133
+ @dataclass
134
+ class SyncPlan:
135
+ """A collection of `Action`s representing the plan produced by the Analyzer.
136
+
137
+ The `SyncPlan` is intentionally minimal: it stores ordered actions and a
138
+ small metadata map that presenters or executors can consume.
139
+ """
140
+
141
+ actions: list[Action] = field(default_factory=list)
142
+ metadata: dict[str, Any] = field(default_factory=dict)
143
+
144
+ def add(self, action: Action) -> None:
145
+ """Add an action to the sync plan.
146
+
147
+ Args:
148
+ action: The action to add.
149
+ """
150
+ self.actions.append(action)
151
+
152
+ def describe(self) -> list[str]:
153
+ """Get textual descriptions of all actions in the plan.
154
+
155
+ Returns:
156
+ List of action descriptions.
157
+ """
158
+ return [a.describe() for a in self.actions]
159
+
160
+ def to_dict(self) -> dict[str, Any]:
161
+ """Convert the sync plan to a dictionary representation.
162
+
163
+ Returns:
164
+ Dictionary with actions and metadata.
165
+ """
166
+ return {
167
+ "actions": [
168
+ {"type": a.action_type, "payload": a.payload} for a in self.actions
169
+ ],
170
+ "metadata": self.metadata,
171
+ }