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,699 @@
1
+ """Comprehensive metrics tracking for sync operations.
2
+
3
+ Tracks all aspects of sync including:
4
+ - Issue deduplication metrics (local, remote, cross-set)
5
+ - Data fetching and syncing metrics
6
+ - Conflict detection and resolution
7
+ - Duplicate detection and resolution
8
+ - Cache hit rates and circuit breaker state
9
+ - Phase timing and performance breakdown
10
+ """
11
+
12
+ import uuid
13
+ from dataclasses import dataclass, field
14
+ from datetime import UTC, datetime
15
+ from typing import Any
16
+
17
+ import structlog
18
+
19
+ logger = structlog.get_logger()
20
+
21
+
22
+ @dataclass
23
+ class SyncMetrics:
24
+ """Comprehensive metrics for a sync operation."""
25
+
26
+ # Operation identification
27
+ operation_id: str = field(default_factory=lambda: str(uuid.uuid4()))
28
+ backend_type: str = "" # "github", "vanilla_git", etc.
29
+ start_time: datetime = field(default_factory=lambda: datetime.now(UTC))
30
+
31
+ # Overall timing
32
+ duration_seconds: float = 0.0
33
+
34
+ # Deduplication metrics - LOCAL
35
+ local_issues_before_dedup: int = 0
36
+ local_issues_after_dedup: int = 0
37
+ local_dedup_reduction_pct: float = 0.0
38
+ local_dedup_phase_duration: float = 0.0
39
+
40
+ # Deduplication metrics - REMOTE
41
+ remote_issues_before_dedup: int = 0
42
+ remote_issues_after_dedup: int = 0
43
+ remote_dedup_reduction_pct: float = 0.0
44
+ remote_dedup_phase_duration: float = 0.0
45
+
46
+ # Deduplication metrics - CROSS-SET
47
+ cross_set_duplicates_detected: int = 0
48
+
49
+ # Duplicate resolution metrics
50
+ duplicates_detected: int = 0
51
+ duplicates_auto_resolved: int = 0
52
+ duplicates_manual_resolved: int = 0
53
+ issues_deleted: int = 0 # Hard deleted (ID collisions)
54
+ issues_archived: int = 0 # Soft deleted (fuzzy matches)
55
+
56
+ # Sync operations
57
+ issues_fetched: int = 0
58
+ fetch_phase_duration: float = 0.0
59
+ issues_pushed: int = 0
60
+ push_phase_duration: float = 0.0
61
+ issues_pulled: int = 0
62
+ pull_phase_duration: float = 0.0
63
+
64
+ # Conflict and error tracking
65
+ conflicts_detected: int = 0
66
+ conflict_resolution_duration: float = 0.0
67
+ errors_count: int = 0
68
+
69
+ # Performance metrics
70
+ cache_hit_rate: float = 0.0
71
+ circuit_breaker_state: str = "closed"
72
+ database_query_time: float = 0.0
73
+ total_api_calls: int = 0
74
+
75
+ # Phase timing
76
+ analysis_phase_duration: float = 0.0
77
+ merge_phase_duration: float = 0.0
78
+
79
+ # Sync link metrics
80
+ sync_links_created: int = 0
81
+ orphaned_links: int = 0
82
+
83
+ # Custom metadata
84
+ metadata: dict[str, Any] = field(default_factory=dict)
85
+
86
+ def calculate_reductions(self) -> None:
87
+ """Calculate deduplication reduction percentages."""
88
+ if self.local_issues_before_dedup > 0:
89
+ self.local_dedup_reduction_pct = (
90
+ (self.local_issues_before_dedup - self.local_issues_after_dedup)
91
+ / self.local_issues_before_dedup
92
+ ) * 100
93
+
94
+ if self.remote_issues_before_dedup > 0:
95
+ self.remote_dedup_reduction_pct = (
96
+ (self.remote_issues_before_dedup - self.remote_issues_after_dedup)
97
+ / self.remote_issues_before_dedup
98
+ ) * 100
99
+
100
+ def to_dict(self) -> dict[str, Any]:
101
+ """Convert metrics to dictionary for storage."""
102
+ return {
103
+ "operation_id": self.operation_id,
104
+ "backend_type": self.backend_type,
105
+ "start_time": self.start_time.isoformat(),
106
+ "duration_seconds": self.duration_seconds,
107
+ # Dedup - Local
108
+ "local_issues_before_dedup": self.local_issues_before_dedup,
109
+ "local_issues_after_dedup": self.local_issues_after_dedup,
110
+ "local_dedup_reduction_pct": self.local_dedup_reduction_pct,
111
+ "local_dedup_phase_duration": self.local_dedup_phase_duration,
112
+ # Dedup - Remote
113
+ "remote_issues_before_dedup": self.remote_issues_before_dedup,
114
+ "remote_issues_after_dedup": self.remote_issues_after_dedup,
115
+ "remote_dedup_reduction_pct": self.remote_dedup_reduction_pct,
116
+ "remote_dedup_phase_duration": self.remote_dedup_phase_duration,
117
+ # Dedup - Cross-set
118
+ "cross_set_duplicates_detected": self.cross_set_duplicates_detected,
119
+ # Duplicate resolution
120
+ "duplicates_detected": self.duplicates_detected,
121
+ "duplicates_auto_resolved": self.duplicates_auto_resolved,
122
+ "duplicates_manual_resolved": self.duplicates_manual_resolved,
123
+ "issues_deleted": self.issues_deleted,
124
+ "issues_archived": self.issues_archived,
125
+ # Sync operations
126
+ "issues_fetched": self.issues_fetched,
127
+ "fetch_phase_duration": self.fetch_phase_duration,
128
+ "issues_pushed": self.issues_pushed,
129
+ "push_phase_duration": self.push_phase_duration,
130
+ "issues_pulled": self.issues_pulled,
131
+ "pull_phase_duration": self.pull_phase_duration,
132
+ # Conflicts/errors
133
+ "conflicts_detected": self.conflicts_detected,
134
+ "conflict_resolution_duration": self.conflict_resolution_duration,
135
+ "errors_count": self.errors_count,
136
+ # Performance
137
+ "cache_hit_rate": self.cache_hit_rate,
138
+ "circuit_breaker_state": self.circuit_breaker_state,
139
+ "database_query_time": self.database_query_time,
140
+ "total_api_calls": self.total_api_calls,
141
+ # Phase timing
142
+ "analysis_phase_duration": self.analysis_phase_duration,
143
+ "merge_phase_duration": self.merge_phase_duration,
144
+ # Sync links
145
+ "sync_links_created": self.sync_links_created,
146
+ "orphaned_links": self.orphaned_links,
147
+ # Metadata
148
+ "metadata": self.metadata,
149
+ }
150
+
151
+
152
+ class SyncObservability:
153
+ """Service for tracking and reporting sync metrics.
154
+
155
+ Provides methods to record metrics at various stages of sync
156
+ and generates comprehensive reports for observability.
157
+ """
158
+
159
+ def __init__(self):
160
+ """Initialize observability tracker."""
161
+ self._operations: dict[str, SyncMetrics] = {}
162
+ self._logger = logger
163
+
164
+ def start_operation(self, backend_type: str) -> str:
165
+ """Start tracking a sync operation.
166
+
167
+ Args:
168
+ backend_type: Type of backend (e.g., "github", "vanilla_git")
169
+
170
+ Returns:
171
+ operation_id for later reference
172
+ """
173
+ metrics = SyncMetrics(backend_type=backend_type)
174
+ self._operations[metrics.operation_id] = metrics
175
+ self._logger.info(
176
+ "sync_operation_started",
177
+ operation_id=metrics.operation_id,
178
+ backend_type=backend_type,
179
+ )
180
+ return metrics.operation_id
181
+
182
+ def record_local_dedup(
183
+ self,
184
+ operation_id: str,
185
+ before: int,
186
+ after: int,
187
+ duration: float,
188
+ ) -> None:
189
+ """Record local issue deduplication metrics.
190
+
191
+ Args:
192
+ operation_id: ID of the sync operation
193
+ before: Number of issues before deduplication
194
+ after: Number of issues after deduplication
195
+ duration: Time taken for deduplication (seconds)
196
+ """
197
+ metrics = self._get_metrics(operation_id)
198
+ if not metrics:
199
+ return
200
+
201
+ metrics.local_issues_before_dedup = before
202
+ metrics.local_issues_after_dedup = after
203
+ metrics.local_dedup_phase_duration = duration
204
+ metrics.calculate_reductions()
205
+
206
+ self._logger.info(
207
+ "local_dedup_recorded",
208
+ operation_id=operation_id,
209
+ before=before,
210
+ after=after,
211
+ reduction_pct=metrics.local_dedup_reduction_pct,
212
+ duration_seconds=duration,
213
+ )
214
+
215
+ def record_remote_dedup(
216
+ self,
217
+ operation_id: str,
218
+ before: int,
219
+ after: int,
220
+ duration: float,
221
+ ) -> None:
222
+ """Record remote issue deduplication metrics.
223
+
224
+ Args:
225
+ operation_id: ID of the sync operation
226
+ before: Number of issues before deduplication
227
+ after: Number of issues after deduplication
228
+ duration: Time taken for deduplication (seconds)
229
+ """
230
+ metrics = self._get_metrics(operation_id)
231
+ if not metrics:
232
+ return
233
+
234
+ metrics.remote_issues_before_dedup = before
235
+ metrics.remote_issues_after_dedup = after
236
+ metrics.remote_dedup_phase_duration = duration
237
+ metrics.calculate_reductions()
238
+
239
+ self._logger.info(
240
+ "remote_dedup_recorded",
241
+ operation_id=operation_id,
242
+ before=before,
243
+ after=after,
244
+ reduction_pct=metrics.remote_dedup_reduction_pct,
245
+ duration_seconds=duration,
246
+ )
247
+
248
+ def record_cross_set_duplicates(
249
+ self,
250
+ operation_id: str,
251
+ count: int,
252
+ ) -> None:
253
+ """Record cross-set duplicate detection metrics.
254
+
255
+ Args:
256
+ operation_id: ID of the sync operation
257
+ count: Number of cross-set duplicates detected
258
+ """
259
+ metrics = self._get_metrics(operation_id)
260
+ if not metrics:
261
+ return
262
+
263
+ metrics.cross_set_duplicates_detected = count
264
+ self._logger.info(
265
+ "cross_set_duplicates_recorded",
266
+ operation_id=operation_id,
267
+ count=count,
268
+ )
269
+
270
+ def record_fetch(
271
+ self,
272
+ operation_id: str,
273
+ count: int,
274
+ duration: float,
275
+ ) -> None:
276
+ """Record data fetch metrics.
277
+
278
+ Args:
279
+ operation_id: ID of the sync operation
280
+ count: Number of issues fetched
281
+ duration: Time taken to fetch (seconds)
282
+ """
283
+ metrics = self._get_metrics(operation_id)
284
+ if not metrics:
285
+ return
286
+
287
+ metrics.issues_fetched = count
288
+ metrics.fetch_phase_duration = duration
289
+
290
+ self._logger.info(
291
+ "fetch_recorded",
292
+ operation_id=operation_id,
293
+ issues_fetched=count,
294
+ duration_seconds=duration,
295
+ )
296
+
297
+ def record_push(
298
+ self,
299
+ operation_id: str,
300
+ count: int,
301
+ duration: float,
302
+ ) -> None:
303
+ """Record push operation metrics.
304
+
305
+ Args:
306
+ operation_id: ID of the sync operation
307
+ count: Number of issues pushed
308
+ duration: Time taken for push (seconds)
309
+ """
310
+ metrics = self._get_metrics(operation_id)
311
+ if not metrics:
312
+ return
313
+
314
+ metrics.issues_pushed = count
315
+ metrics.push_phase_duration = duration
316
+
317
+ self._logger.info(
318
+ "push_recorded",
319
+ operation_id=operation_id,
320
+ issues_pushed=count,
321
+ duration_seconds=duration,
322
+ )
323
+
324
+ def record_pull(
325
+ self,
326
+ operation_id: str,
327
+ count: int,
328
+ duration: float,
329
+ ) -> None:
330
+ """Record pull operation metrics.
331
+
332
+ Args:
333
+ operation_id: ID of the sync operation
334
+ count: Number of issues pulled
335
+ duration: Time taken for pull (seconds)
336
+ """
337
+ metrics = self._get_metrics(operation_id)
338
+ if not metrics:
339
+ return
340
+
341
+ metrics.issues_pulled = count
342
+ metrics.pull_phase_duration = duration
343
+
344
+ self._logger.info(
345
+ "pull_recorded",
346
+ operation_id=operation_id,
347
+ issues_pulled=count,
348
+ duration_seconds=duration,
349
+ )
350
+
351
+ def record_conflict(
352
+ self,
353
+ operation_id: str,
354
+ count: int = 1,
355
+ resolution_duration: float = 0.0,
356
+ ) -> None:
357
+ """Record conflict detection metrics.
358
+
359
+ Args:
360
+ operation_id: ID of the sync operation
361
+ count: Number of conflicts detected (default: 1)
362
+ resolution_duration: Time taken to resolve conflicts (seconds)
363
+ """
364
+ metrics = self._get_metrics(operation_id)
365
+ if not metrics:
366
+ return
367
+
368
+ metrics.conflicts_detected += count
369
+ metrics.conflict_resolution_duration += resolution_duration
370
+
371
+ self._logger.info(
372
+ "conflict_recorded",
373
+ operation_id=operation_id,
374
+ count=count,
375
+ resolution_duration_seconds=resolution_duration,
376
+ )
377
+
378
+ def record_duplicate_detected(
379
+ self,
380
+ operation_id: str,
381
+ count: int = 1,
382
+ ) -> None:
383
+ """Record duplicate detection metrics.
384
+
385
+ Args:
386
+ operation_id: ID of the sync operation
387
+ count: Number of duplicates detected (default: 1)
388
+ """
389
+ metrics = self._get_metrics(operation_id)
390
+ if not metrics:
391
+ return
392
+
393
+ metrics.duplicates_detected += count
394
+ self._logger.info(
395
+ "duplicate_detected",
396
+ operation_id=operation_id,
397
+ count=count,
398
+ )
399
+
400
+ def record_duplicate_resolved(
401
+ self,
402
+ operation_id: str,
403
+ count: int = 1,
404
+ auto_resolved: int = 0,
405
+ deleted: int = 0,
406
+ archived: int = 0,
407
+ ) -> None:
408
+ """Record duplicate resolution metrics.
409
+
410
+ Args:
411
+ operation_id: ID of the sync operation
412
+ count: Total number of duplicates resolved
413
+ auto_resolved: Number auto-resolved
414
+ deleted: Number hard-deleted
415
+ archived: Number soft-archived
416
+ """
417
+ metrics = self._get_metrics(operation_id)
418
+ if not metrics:
419
+ return
420
+
421
+ metrics.duplicates_auto_resolved += auto_resolved
422
+ metrics.duplicates_manual_resolved += count - auto_resolved
423
+ metrics.issues_deleted += deleted
424
+ metrics.issues_archived += archived
425
+
426
+ self._logger.info(
427
+ "duplicate_resolved",
428
+ operation_id=operation_id,
429
+ total_count=count,
430
+ auto_resolved=auto_resolved,
431
+ manual_resolved=(count - auto_resolved),
432
+ deleted=deleted,
433
+ archived=archived,
434
+ )
435
+
436
+ def record_error(
437
+ self,
438
+ operation_id: str,
439
+ error_type: str,
440
+ error_message: str = "",
441
+ ) -> None:
442
+ """Record error metrics.
443
+
444
+ Args:
445
+ operation_id: ID of the sync operation
446
+ error_type: Type of error that occurred
447
+ error_message: Optional error message
448
+ """
449
+ metrics = self._get_metrics(operation_id)
450
+ if not metrics:
451
+ return
452
+
453
+ metrics.errors_count += 1
454
+ self._logger.warning(
455
+ "sync_error_recorded",
456
+ operation_id=operation_id,
457
+ error_type=error_type,
458
+ error_message=error_message,
459
+ )
460
+
461
+ def record_phase_timing(
462
+ self,
463
+ operation_id: str,
464
+ phase_name: str,
465
+ duration: float,
466
+ ) -> None:
467
+ """Record timing for a sync phase.
468
+
469
+ Args:
470
+ operation_id: ID of the sync operation
471
+ phase_name: Name of the phase ("analysis", "merge", etc.)
472
+ duration: Duration of the phase (seconds)
473
+ """
474
+ metrics = self._get_metrics(operation_id)
475
+ if not metrics:
476
+ return
477
+
478
+ if phase_name == "analysis":
479
+ metrics.analysis_phase_duration = duration
480
+ elif phase_name == "merge":
481
+ metrics.merge_phase_duration = duration
482
+
483
+ self._logger.info(
484
+ "phase_timing_recorded",
485
+ operation_id=operation_id,
486
+ phase_name=phase_name,
487
+ duration_seconds=duration,
488
+ )
489
+
490
+ def record_cache_stats(
491
+ self,
492
+ operation_id: str,
493
+ hit_rate: float,
494
+ ) -> None:
495
+ """Record cache performance metrics.
496
+
497
+ Args:
498
+ operation_id: ID of the sync operation
499
+ hit_rate: Cache hit rate (0.0-1.0)
500
+ """
501
+ metrics = self._get_metrics(operation_id)
502
+ if not metrics:
503
+ return
504
+
505
+ metrics.cache_hit_rate = hit_rate
506
+ self._logger.info(
507
+ "cache_stats_recorded",
508
+ operation_id=operation_id,
509
+ hit_rate=hit_rate,
510
+ )
511
+
512
+ def record_circuit_breaker_state(
513
+ self,
514
+ operation_id: str,
515
+ state: str,
516
+ ) -> None:
517
+ """Record circuit breaker state metrics.
518
+
519
+ Args:
520
+ operation_id: ID of the sync operation
521
+ state: State of the circuit breaker ("closed", "open", "half-open")
522
+ """
523
+ metrics = self._get_metrics(operation_id)
524
+ if not metrics:
525
+ return
526
+
527
+ metrics.circuit_breaker_state = state
528
+ self._logger.debug(
529
+ "circuit_breaker_state_recorded",
530
+ operation_id=operation_id,
531
+ state=state,
532
+ )
533
+
534
+ def record_sync_links(
535
+ self,
536
+ operation_id: str,
537
+ created_count: int | None = None,
538
+ orphaned: int = 0,
539
+ *args: int,
540
+ created: int | None = None,
541
+ ) -> None:
542
+ """Record sync link metrics.
543
+
544
+ Args:
545
+ operation_id: ID of the sync operation
546
+ created_count: Number of sync links created
547
+ created: Deprecated alias for created_count
548
+ orphaned: Number of orphaned links (default: 0)
549
+ """
550
+ if args:
551
+ if created_count is None:
552
+ created_count = args[0]
553
+ if len(args) > 1:
554
+ orphaned = args[1]
555
+
556
+ if created_count is None and created is not None:
557
+ created_count = created
558
+ if created_count is None:
559
+ created_count = 0
560
+
561
+ metrics = self._get_metrics(operation_id)
562
+ if not metrics:
563
+ return
564
+
565
+ metrics.sync_links_created = created_count
566
+ metrics.orphaned_links = orphaned
567
+
568
+ self._logger.info(
569
+ "sync_links_recorded",
570
+ operation_id=operation_id,
571
+ created_count=created_count,
572
+ orphaned=orphaned,
573
+ )
574
+
575
+ def record_metadata(
576
+ self,
577
+ operation_id: str,
578
+ key: str,
579
+ value: Any,
580
+ ) -> None:
581
+ """Record custom metadata.
582
+
583
+ Args:
584
+ operation_id: ID of the sync operation
585
+ key: Metadata key
586
+ value: Metadata value
587
+ """
588
+ metrics = self._get_metrics(operation_id)
589
+ if not metrics:
590
+ return
591
+
592
+ metrics.metadata[key] = value
593
+
594
+ def finalize(self, operation_id: str) -> SyncMetrics:
595
+ """Finalize and return metrics for a completed operation.
596
+
597
+ Args:
598
+ operation_id: ID of the sync operation
599
+
600
+ Returns:
601
+ Completed SyncMetrics object
602
+ """
603
+ metrics = self._get_metrics(operation_id)
604
+ if not metrics:
605
+ return SyncMetrics()
606
+
607
+ end_time = datetime.now(UTC)
608
+ metrics.duration_seconds = (end_time - metrics.start_time).total_seconds()
609
+
610
+ self._logger.info(
611
+ "sync_operation_completed",
612
+ operation_id=operation_id,
613
+ duration_seconds=metrics.duration_seconds,
614
+ issues_fetched=metrics.issues_fetched,
615
+ issues_pushed=metrics.issues_pushed,
616
+ issues_pulled=metrics.issues_pulled,
617
+ conflicts_detected=metrics.conflicts_detected,
618
+ duplicates_detected=metrics.duplicates_detected,
619
+ errors_count=metrics.errors_count,
620
+ )
621
+
622
+ return metrics
623
+
624
+ def get_metrics(self, operation_id: str) -> SyncMetrics | None:
625
+ """Retrieve metrics for an operation.
626
+
627
+ Args:
628
+ operation_id: ID of the sync operation
629
+
630
+ Returns:
631
+ SyncMetrics if found, None otherwise
632
+ """
633
+ return self._get_metrics(operation_id)
634
+
635
+ def _get_metrics(self, operation_id: str) -> SyncMetrics | None:
636
+ """Internal method to get metrics by operation ID.
637
+
638
+ Args:
639
+ operation_id: ID of the sync operation
640
+
641
+ Returns:
642
+ SyncMetrics if found, None otherwise
643
+ """
644
+ return self._operations.get(operation_id)
645
+
646
+ def clear_old_operations(self, max_age_seconds: float = 86400) -> int:
647
+ """Clear metrics for operations older than max_age_seconds.
648
+
649
+ Args:
650
+ max_age_seconds: Maximum age of operations to keep (default: 1 day)
651
+
652
+ Returns:
653
+ Number of operations cleared
654
+ """
655
+ now = datetime.now(UTC)
656
+ to_remove = []
657
+
658
+ for op_id, metrics in self._operations.items():
659
+ age = (now - metrics.start_time).total_seconds()
660
+ if age > max_age_seconds:
661
+ to_remove.append(op_id)
662
+
663
+ for op_id in to_remove:
664
+ del self._operations[op_id]
665
+
666
+ if to_remove:
667
+ self._logger.info(
668
+ "old_operations_cleared",
669
+ count=len(to_remove),
670
+ max_age_seconds=max_age_seconds,
671
+ )
672
+
673
+ return len(to_remove)
674
+
675
+
676
+ # Global instance for use throughout the application
677
+ _global_observability: SyncObservability | None = None
678
+
679
+
680
+ def get_observability() -> SyncObservability:
681
+ """Get or create global observability instance.
682
+
683
+ Returns:
684
+ SyncObservability singleton
685
+ """
686
+ global _global_observability
687
+ if _global_observability is None:
688
+ _global_observability = SyncObservability()
689
+ return _global_observability
690
+
691
+
692
+ def set_observability(observability: SyncObservability) -> None:
693
+ """Set global observability instance (useful for testing).
694
+
695
+ Args:
696
+ observability: SyncObservability instance to use globally
697
+ """
698
+ global _global_observability
699
+ _global_observability = observability