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,207 @@
1
+ """Sync state and file synchronization operations."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from roadmap.common.errors import OperationType, safe_operation
7
+ from roadmap.common.logging import get_logger
8
+
9
+ from ..file_synchronizer import FileSynchronizer
10
+ from ..repositories import SyncStateRepository
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class SyncStateStorage:
16
+ """Handles sync state tracking and file synchronization operations."""
17
+
18
+ def __init__(
19
+ self,
20
+ sync_state_repo: SyncStateRepository,
21
+ file_synchronizer: FileSynchronizer,
22
+ ):
23
+ """Initialize sync state storage.
24
+
25
+ Args:
26
+ sync_state_repo: SyncStateRepository instance for sync state persistence
27
+ file_synchronizer: FileSynchronizer instance for file operations
28
+ """
29
+ self._sync_state_repo = sync_state_repo
30
+ self._file_synchronizer = file_synchronizer
31
+ logger.debug("sync_state_storage_initialized")
32
+
33
+ # Sync state operations
34
+ def get_sync_state(self, key: str) -> str | None:
35
+ """Get sync state value.
36
+
37
+ Args:
38
+ key: State key identifier
39
+
40
+ Returns:
41
+ str: State value or None if not found
42
+ """
43
+ logger.debug("Getting sync state", key=key)
44
+ return self._sync_state_repo.get(key)
45
+
46
+ def set_sync_state(self, key: str, value: str):
47
+ """Set sync state value.
48
+
49
+ Args:
50
+ key: State key identifier
51
+ value: State value to store
52
+ """
53
+ logger.debug("Setting sync state", key=key, value_length=len(value))
54
+ self._sync_state_repo.set(key, value)
55
+
56
+ # File sync operations
57
+ def get_file_sync_status(self, file_path: str) -> dict[str, Any] | None:
58
+ """Get sync status for a file.
59
+
60
+ Args:
61
+ file_path: Path to file
62
+
63
+ Returns:
64
+ dict: Sync status data or None if not found
65
+ """
66
+ logger.debug("Getting file sync status", file_path=file_path)
67
+ return self._file_synchronizer.get_file_sync_status(file_path)
68
+
69
+ @safe_operation(OperationType.UPDATE, "FileSync")
70
+ def update_file_sync_status(
71
+ self, file_path: str, content_hash: str, file_size: int, last_modified: Any
72
+ ):
73
+ """Update sync status for a file.
74
+
75
+ Args:
76
+ file_path: Path to file
77
+ content_hash: Hash of file content
78
+ file_size: Size of file in bytes
79
+ last_modified: Last modification timestamp
80
+ """
81
+ logger.debug(
82
+ "Updating file sync status", file_path=file_path, file_size=file_size
83
+ )
84
+ self._file_synchronizer.update_file_sync_status(
85
+ file_path, content_hash, file_size, last_modified
86
+ )
87
+
88
+ def has_file_changed(self, file_path: Path) -> bool:
89
+ """Check if file has changed since last sync.
90
+
91
+ Args:
92
+ file_path: Path to file
93
+
94
+ Returns:
95
+ bool: True if file has changed
96
+ """
97
+ result = self._file_synchronizer.has_file_changed(file_path)
98
+ logger.debug("File change check", file_path=str(file_path), changed=result)
99
+ return result
100
+
101
+ @safe_operation(OperationType.SYNC, "Directory", retryable=True, max_retries=2)
102
+ def sync_directory_incremental(self, roadmap_dir: Path) -> dict[str, Any]:
103
+ """Incrementally sync .roadmap directory to database.
104
+
105
+ Args:
106
+ roadmap_dir: Path to .roadmap directory
107
+
108
+ Returns:
109
+ dict: Sync results with files_synced count
110
+ """
111
+ logger.info("Incremental sync started", roadmap_dir=str(roadmap_dir))
112
+ result = self._file_synchronizer.sync_directory_incremental(roadmap_dir)
113
+ logger.info(
114
+ "Incremental sync completed",
115
+ roadmap_dir=str(roadmap_dir),
116
+ files_synced=result.get("files_synced", 0),
117
+ )
118
+ return result
119
+
120
+ @safe_operation(OperationType.SYNC, "Directory", retryable=True, max_retries=2)
121
+ def full_rebuild_from_git(self, roadmap_dir: Path) -> dict[str, Any]:
122
+ """Full rebuild of database from git files.
123
+
124
+ Args:
125
+ roadmap_dir: Path to .roadmap directory
126
+
127
+ Returns:
128
+ dict: Rebuild results with files_processed count
129
+ """
130
+ logger.info("Full rebuild from git started", roadmap_dir=str(roadmap_dir))
131
+ result = self._file_synchronizer.full_rebuild_from_git(roadmap_dir)
132
+ logger.info(
133
+ "Full rebuild completed",
134
+ roadmap_dir=str(roadmap_dir),
135
+ files_processed=result.get("files_processed", 0),
136
+ )
137
+ return result
138
+
139
+ def should_do_full_rebuild(self, roadmap_dir: Path, threshold: int = 50) -> bool:
140
+ """Determine if full rebuild is needed vs incremental sync.
141
+
142
+ Args:
143
+ roadmap_dir: Path to .roadmap directory
144
+ threshold: Number of changed files before full rebuild recommended
145
+
146
+ Returns:
147
+ bool: True if full rebuild is recommended
148
+ """
149
+ result = self._file_synchronizer.should_do_full_rebuild(roadmap_dir, threshold)
150
+ logger.debug("Full rebuild check", roadmap_dir=str(roadmap_dir), result=result)
151
+ return result
152
+
153
+ @safe_operation(OperationType.SYNC, "Directory")
154
+ def smart_sync(self, roadmap_dir: Path | None = None) -> dict[str, Any]:
155
+ """Smart sync that chooses between incremental and full rebuild.
156
+
157
+ Args:
158
+ roadmap_dir: Path to .roadmap directory. Defaults to .roadmap in cwd
159
+
160
+ Returns:
161
+ dict: Sync results
162
+ """
163
+ if roadmap_dir is None:
164
+ roadmap_dir = Path.cwd() / ".roadmap"
165
+
166
+ logger.info("Smart sync started", roadmap_dir=str(roadmap_dir))
167
+
168
+ try:
169
+ if self.should_do_full_rebuild(roadmap_dir):
170
+ logger.info("Choosing full rebuild", roadmap_dir=str(roadmap_dir))
171
+ result = self.full_rebuild_from_git(roadmap_dir)
172
+ else:
173
+ logger.info("Choosing incremental sync", roadmap_dir=str(roadmap_dir))
174
+ result = self.sync_directory_incremental(roadmap_dir)
175
+
176
+ logger.info(
177
+ "Smart sync completed",
178
+ roadmap_dir=str(roadmap_dir),
179
+ result_keys=list(result.keys()),
180
+ )
181
+ return result
182
+
183
+ except Exception as e:
184
+ logger.error(
185
+ "Smart sync failed",
186
+ roadmap_dir=str(roadmap_dir),
187
+ error=str(e),
188
+ error_type=type(e).__name__,
189
+ )
190
+ return {"error": str(e), "files_failed": 1}
191
+
192
+ # File changes checking - needs state manager context
193
+ def has_file_changes(self, state_manager) -> bool:
194
+ """Check if .roadmap/ files have changes since last sync.
195
+
196
+ Args:
197
+ state_manager: StateManager instance for database access
198
+
199
+ Returns:
200
+ bool: True if changes detected
201
+ """
202
+ from .queries import QueryService
203
+
204
+ logger.debug("Checking file changes")
205
+ result = QueryService(state_manager).has_file_changes()
206
+ logger.debug("File changes check completed", has_changes=result)
207
+ return result
@@ -0,0 +1,342 @@
1
+ """Repository for persisting and querying sync metrics.
2
+
3
+ This module provides database persistence for sync operation metrics,
4
+ allowing historical tracking and analysis of sync operations.
5
+ """
6
+
7
+ import json
8
+ import uuid
9
+ from datetime import UTC, datetime, timedelta
10
+ from typing import Any
11
+
12
+ from roadmap.adapters.persistence.database_manager import DatabaseManager
13
+ from roadmap.common.logging import get_logger
14
+ from roadmap.core.observability.sync_metrics import SyncMetrics
15
+
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ class SyncMetricsRepository:
20
+ """Repository for sync metrics persistence and queries."""
21
+
22
+ def __init__(self, db_manager: DatabaseManager):
23
+ """Initialize the repository.
24
+
25
+ Args:
26
+ db_manager: DatabaseManager instance for database access
27
+ """
28
+ self.db_manager = db_manager
29
+
30
+ def save(self, metrics: SyncMetrics) -> bool:
31
+ """Save sync metrics to database.
32
+
33
+ Args:
34
+ metrics: SyncMetrics object to persist
35
+
36
+ Returns:
37
+ True if successful, False otherwise
38
+ """
39
+ try:
40
+ record_id = str(uuid.uuid4())
41
+ metrics_dict = metrics.to_dict()
42
+ metrics_json = json.dumps(metrics_dict)
43
+
44
+ with self.db_manager.transaction() as conn:
45
+ conn.execute(
46
+ """
47
+ INSERT INTO sync_metrics (
48
+ id,
49
+ operation_id,
50
+ backend_type,
51
+ duration_seconds,
52
+ metrics_json,
53
+ created_at
54
+ ) VALUES (?, ?, ?, ?, ?, ?)
55
+ """,
56
+ (
57
+ record_id,
58
+ metrics.operation_id,
59
+ metrics.backend_type,
60
+ metrics.duration_seconds,
61
+ metrics_json,
62
+ datetime.now(UTC).isoformat(),
63
+ ),
64
+ )
65
+
66
+ logger.info(
67
+ "sync_metrics_saved",
68
+ operation_id=metrics.operation_id,
69
+ backend_type=metrics.backend_type,
70
+ duration_seconds=metrics.duration_seconds,
71
+ )
72
+ return True
73
+
74
+ except Exception as e:
75
+ logger.error(
76
+ "sync_metrics_save_failed",
77
+ error=str(e),
78
+ operation_id=metrics.operation_id,
79
+ severity="operational",
80
+ )
81
+ return False
82
+
83
+ def get_latest(self, backend_type: str | None = None) -> SyncMetrics | None:
84
+ """Get the most recent sync metrics.
85
+
86
+ Args:
87
+ backend_type: Optional filter by backend type
88
+
89
+ Returns:
90
+ SyncMetrics if found, None otherwise
91
+ """
92
+ try:
93
+ conn = self.db_manager._get_connection()
94
+
95
+ query = "SELECT metrics_json FROM sync_metrics"
96
+ params = []
97
+
98
+ if backend_type:
99
+ query += " WHERE backend_type = ?"
100
+ params.append(backend_type)
101
+
102
+ query += " ORDER BY created_at DESC LIMIT 1"
103
+
104
+ cursor = conn.execute(query, params)
105
+ row = cursor.fetchone()
106
+
107
+ if not row:
108
+ return None
109
+
110
+ metrics_dict = json.loads(row[0])
111
+ return self._dict_to_metrics(metrics_dict)
112
+
113
+ except Exception as e:
114
+ logger.error(
115
+ "sync_metrics_get_latest_failed",
116
+ error=str(e),
117
+ backend_type=backend_type,
118
+ severity="operational",
119
+ )
120
+ return None
121
+
122
+ def list_by_date(
123
+ self, backend_type: str | None = None, days: int = 7
124
+ ) -> list[SyncMetrics]:
125
+ """List sync metrics from the last N days.
126
+
127
+ Args:
128
+ backend_type: Optional filter by backend type
129
+ days: Number of days to look back (default: 7)
130
+
131
+ Returns:
132
+ List of SyncMetrics objects
133
+ """
134
+ try:
135
+ conn = self.db_manager._get_connection()
136
+ cutoff_date = (datetime.now(UTC) - timedelta(days=days)).isoformat()
137
+
138
+ query = "SELECT metrics_json FROM sync_metrics WHERE created_at >= ?"
139
+ params = [cutoff_date]
140
+
141
+ if backend_type:
142
+ query += " AND backend_type = ?"
143
+ params.append(backend_type)
144
+
145
+ query += " ORDER BY created_at DESC"
146
+
147
+ cursor = conn.execute(query, params)
148
+ rows = cursor.fetchall()
149
+
150
+ metrics_list = []
151
+ for row in rows:
152
+ try:
153
+ metrics_dict = json.loads(row[0])
154
+ metrics = self._dict_to_metrics(metrics_dict)
155
+ metrics_list.append(metrics)
156
+ except Exception as e:
157
+ logger.warning(
158
+ "sync_metrics_deserialization_failed",
159
+ error=str(e),
160
+ severity="operational",
161
+ )
162
+
163
+ return metrics_list
164
+
165
+ except Exception as e:
166
+ logger.error(
167
+ "sync_metrics_list_by_date_failed",
168
+ error=str(e),
169
+ backend_type=backend_type,
170
+ days=days,
171
+ severity="operational",
172
+ )
173
+ return []
174
+
175
+ def get_by_operation_id(self, operation_id: str) -> SyncMetrics | None:
176
+ """Get metrics for a specific operation ID.
177
+
178
+ Args:
179
+ operation_id: The operation ID to look up
180
+
181
+ Returns:
182
+ SyncMetrics if found, None otherwise
183
+ """
184
+ try:
185
+ conn = self.db_manager._get_connection()
186
+
187
+ cursor = conn.execute(
188
+ "SELECT metrics_json FROM sync_metrics WHERE operation_id = ? LIMIT 1",
189
+ (operation_id,),
190
+ )
191
+ row = cursor.fetchone()
192
+
193
+ if not row:
194
+ return None
195
+
196
+ metrics_dict = json.loads(row[0])
197
+ return self._dict_to_metrics(metrics_dict)
198
+
199
+ except Exception as e:
200
+ logger.error(
201
+ "sync_metrics_get_by_operation_id_failed",
202
+ error=str(e),
203
+ operation_id=operation_id,
204
+ severity="operational",
205
+ )
206
+ return None
207
+
208
+ def get_statistics(
209
+ self, backend_type: str | None = None, days: int = 30
210
+ ) -> dict[str, Any]:
211
+ """Get aggregate statistics for sync metrics.
212
+
213
+ Args:
214
+ backend_type: Optional filter by backend type
215
+ days: Number of days to analyze (default: 30)
216
+
217
+ Returns:
218
+ Dictionary with statistics
219
+ """
220
+ try:
221
+ conn = self.db_manager._get_connection()
222
+ cutoff_date = (datetime.now(UTC) - timedelta(days=days)).isoformat()
223
+
224
+ query = "SELECT metrics_json FROM sync_metrics WHERE created_at >= ?"
225
+ params = [cutoff_date]
226
+
227
+ if backend_type:
228
+ query += " AND backend_type = ?"
229
+ params.append(backend_type)
230
+
231
+ cursor = conn.execute(query, params)
232
+ rows = cursor.fetchall()
233
+
234
+ if not rows:
235
+ return {
236
+ "total_syncs": 0,
237
+ "avg_duration_seconds": 0.0,
238
+ "total_duplicates_detected": 0,
239
+ "total_conflicts_detected": 0,
240
+ "total_errors": 0,
241
+ }
242
+
243
+ total_duration = 0.0
244
+ total_duplicates = 0
245
+ total_conflicts = 0
246
+ total_errors = 0
247
+ total_syncs = 0
248
+
249
+ for row in rows:
250
+ try:
251
+ metrics_dict = json.loads(row[0])
252
+ total_duration += metrics_dict.get("duration_seconds", 0.0)
253
+ total_duplicates += metrics_dict.get("duplicates_detected", 0)
254
+ total_conflicts += metrics_dict.get("conflicts_detected", 0)
255
+ total_errors += metrics_dict.get("errors_count", 0)
256
+ total_syncs += 1
257
+ except Exception as e:
258
+ logger.warning(
259
+ "sync_metrics_stats_parse_failed",
260
+ error=str(e),
261
+ severity="operational",
262
+ )
263
+
264
+ return {
265
+ "total_syncs": total_syncs,
266
+ "avg_duration_seconds": (
267
+ total_duration / total_syncs if total_syncs > 0 else 0.0
268
+ ),
269
+ "total_duplicates_detected": total_duplicates,
270
+ "total_conflicts_detected": total_conflicts,
271
+ "total_errors": total_errors,
272
+ "backends": backend_type or "all",
273
+ "period_days": days,
274
+ }
275
+
276
+ except Exception as e:
277
+ logger.error(
278
+ "sync_metrics_get_statistics_failed",
279
+ error=str(e),
280
+ backend_type=backend_type,
281
+ days=days,
282
+ severity="operational",
283
+ )
284
+ return {}
285
+
286
+ def delete_old_metrics(self, days: int = 90) -> int:
287
+ """Delete metrics older than N days.
288
+
289
+ Args:
290
+ days: Number of days to keep (default: 90)
291
+
292
+ Returns:
293
+ Number of records deleted
294
+ """
295
+ try:
296
+ cutoff_date = (datetime.now(UTC) - timedelta(days=days)).isoformat()
297
+
298
+ with self.db_manager.transaction() as conn:
299
+ cursor = conn.execute(
300
+ "DELETE FROM sync_metrics WHERE created_at < ?",
301
+ (cutoff_date,),
302
+ )
303
+ deleted_count = cursor.rowcount
304
+
305
+ logger.info(
306
+ "sync_metrics_old_records_deleted",
307
+ deleted_count=deleted_count,
308
+ days=days,
309
+ )
310
+ return deleted_count
311
+
312
+ except Exception as e:
313
+ logger.error(
314
+ "sync_metrics_delete_old_failed",
315
+ error=str(e),
316
+ days=days,
317
+ severity="operational",
318
+ )
319
+ return 0
320
+
321
+ @staticmethod
322
+ def _dict_to_metrics(metrics_dict: dict[str, Any]) -> SyncMetrics:
323
+ """Convert dictionary back to SyncMetrics object.
324
+
325
+ Args:
326
+ metrics_dict: Dictionary from metrics_json
327
+
328
+ Returns:
329
+ SyncMetrics object
330
+ """
331
+ # Extract main fields
332
+ metrics = SyncMetrics(
333
+ operation_id=metrics_dict.get("operation_id", ""),
334
+ backend_type=metrics_dict.get("backend_type", ""),
335
+ )
336
+
337
+ # Set all tracked fields from dictionary
338
+ for key, value in metrics_dict.items():
339
+ if hasattr(metrics, key):
340
+ setattr(metrics, key, value)
341
+
342
+ return metrics