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,161 @@
1
+ """Metrics collection and reporting for monitoring operation performance."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import UTC, datetime
5
+ from typing import Any
6
+
7
+ from ..logging import get_logger
8
+
9
+ logger = get_logger(__name__)
10
+
11
+
12
+ @dataclass
13
+ class OperationMetric:
14
+ """Track operation performance and success.
15
+
16
+ Attributes:
17
+ operation: Name of the operation being tracked
18
+ duration_ms: Duration of the operation in milliseconds
19
+ success: Whether the operation succeeded
20
+ error: Error message if operation failed, None otherwise
21
+ timestamp: When the operation completed (UTC)
22
+ metadata: Additional context about the operation
23
+ """
24
+
25
+ operation: str
26
+ duration_ms: float
27
+ success: bool
28
+ error: str | None = None
29
+ timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
30
+ metadata: dict[str, Any] = field(default_factory=dict)
31
+
32
+
33
+ class MetricsCollector:
34
+ """Collect and report application metrics.
35
+
36
+ This class maintains a collection of operation metrics and provides
37
+ aggregated statistics for monitoring and observability.
38
+ """
39
+
40
+ def __init__(self):
41
+ """Initialize an empty metrics collector."""
42
+ self.metrics: list[OperationMetric] = []
43
+ logger.debug("metrics_collector_initialized")
44
+
45
+ def record(self, metric: OperationMetric) -> None:
46
+ """Record a metric.
47
+
48
+ Args:
49
+ metric: The operation metric to record
50
+ """
51
+ self.metrics.append(metric)
52
+ logger.debug(
53
+ "metric_recorded",
54
+ operation=metric.operation,
55
+ duration_ms=metric.duration_ms,
56
+ success=metric.success,
57
+ error=metric.error,
58
+ )
59
+
60
+ def get_stats(self) -> dict[str, Any]:
61
+ """Get aggregated statistics from recorded metrics.
62
+
63
+ Returns:
64
+ Dictionary containing:
65
+ - total_operations: Total number of operations recorded
66
+ - success_rate: Percentage of successful operations (0.0-1.0)
67
+ - avg_duration_ms: Average operation duration in milliseconds
68
+ - errors: List of failed operation metrics
69
+ - operations_by_type: Count of operations grouped by type
70
+ """
71
+ if not self.metrics:
72
+ return {
73
+ "total_operations": 0,
74
+ "success_rate": 0.0,
75
+ "avg_duration_ms": 0.0,
76
+ "errors": [],
77
+ "operations_by_type": {},
78
+ }
79
+
80
+ total = len(self.metrics)
81
+ successful = sum(1 for m in self.metrics if m.success)
82
+ success_rate = successful / total if total > 0 else 0.0
83
+ avg_duration = sum(m.duration_ms for m in self.metrics) / total
84
+ errors = [m for m in self.metrics if not m.success]
85
+
86
+ # Count operations by type
87
+ operations_by_type: dict[str, int] = {}
88
+ for metric in self.metrics:
89
+ operations_by_type[metric.operation] = (
90
+ operations_by_type.get(metric.operation, 0) + 1
91
+ )
92
+
93
+ return {
94
+ "total_operations": total,
95
+ "success_rate": success_rate,
96
+ "avg_duration_ms": avg_duration,
97
+ "errors": errors,
98
+ "operations_by_type": operations_by_type,
99
+ }
100
+
101
+ def clear(self) -> None:
102
+ """Clear all recorded metrics."""
103
+ count = len(self.metrics)
104
+ self.metrics.clear()
105
+ logger.debug("metrics_cleared", count=count)
106
+
107
+ def get_error_rate(self) -> float:
108
+ """Get the error rate (1 - success_rate).
109
+
110
+ Returns:
111
+ Error rate as a float between 0.0 and 1.0
112
+ """
113
+ if not self.metrics:
114
+ return 0.0
115
+ return 1.0 - self.get_stats()["success_rate"]
116
+
117
+ def get_operation_stats(self, operation: str) -> dict[str, Any]:
118
+ """Get statistics for a specific operation type.
119
+
120
+ Args:
121
+ operation: Name of the operation to get stats for
122
+
123
+ Returns:
124
+ Dictionary with stats specific to this operation type
125
+ """
126
+ op_metrics = [m for m in self.metrics if m.operation == operation]
127
+
128
+ if not op_metrics:
129
+ return {
130
+ "count": 0,
131
+ "success_rate": 0.0,
132
+ "avg_duration_ms": 0.0,
133
+ "errors": [],
134
+ }
135
+
136
+ total = len(op_metrics)
137
+ successful = sum(1 for m in op_metrics if m.success)
138
+
139
+ return {
140
+ "count": total,
141
+ "success_rate": successful / total,
142
+ "avg_duration_ms": sum(m.duration_ms for m in op_metrics) / total,
143
+ "errors": [m for m in op_metrics if not m.success],
144
+ }
145
+
146
+
147
+ # Global metrics collector instance
148
+ _global_collector: MetricsCollector | None = None
149
+
150
+
151
+ def get_metrics_collector() -> MetricsCollector:
152
+ """Get or create the global metrics collector instance.
153
+
154
+ Returns:
155
+ The global MetricsCollector instance
156
+ """
157
+ global _global_collector
158
+ if _global_collector is None:
159
+ _global_collector = MetricsCollector()
160
+ logger.debug("global_metrics_collector_created")
161
+ return _global_collector
@@ -0,0 +1,219 @@
1
+ """Performance monitoring decorators and utilities."""
2
+
3
+ import functools
4
+ import time
5
+ from collections.abc import Callable
6
+ from typing import Any, TypeVar
7
+
8
+ from ..logging import get_logger
9
+ from .metrics import OperationMetric, get_metrics_collector
10
+ from .profiling import get_profiler
11
+
12
+ logger = get_logger(__name__)
13
+
14
+ F = TypeVar("F", bound=Callable[..., Any])
15
+
16
+
17
+ def timed_operation(operation_name: str | None = None, record_metric: bool = True):
18
+ """Time operations and optionally record metrics.
19
+
20
+ This decorator measures the execution time of a function and logs it.
21
+ It can also record the timing as a metric in the global MetricsCollector
22
+ and performance profiler.
23
+
24
+ Args:
25
+ operation_name: Name for the operation (defaults to function name)
26
+ record_metric: Whether to record the timing as a metric
27
+
28
+ Example:
29
+ @timed_operation("create_issue")
30
+ def create_issue(title: str) -> Issue:
31
+ # ... implementation
32
+ pass
33
+ """
34
+
35
+ def decorator(func: F) -> F:
36
+ @functools.wraps(func)
37
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
38
+ op_name = operation_name or func.__name__
39
+ start_time = time.perf_counter()
40
+ error: Exception | None = None
41
+ result: Any = None
42
+
43
+ # Record in profiler
44
+ profiler = get_profiler()
45
+ profiler.start_operation(op_name)
46
+
47
+ try:
48
+ result = func(*args, **kwargs)
49
+ return result
50
+ except Exception as e:
51
+ error = e
52
+ raise
53
+ finally:
54
+ # Calculate duration
55
+ duration_ms = (time.perf_counter() - start_time) * 1000
56
+
57
+ # Record in profiler
58
+ profiler.end_operation(op_name, error=error is not None)
59
+
60
+ # Log the timing
61
+ if error:
62
+ logger.error(
63
+ "operation_failed",
64
+ operation=op_name,
65
+ duration_ms=round(duration_ms, 2),
66
+ error=str(error),
67
+ error_type=type(error).__name__,
68
+ )
69
+ else:
70
+ logger.info(
71
+ "operation_completed",
72
+ operation=op_name,
73
+ duration_ms=round(duration_ms, 2),
74
+ )
75
+
76
+ # Record metric if requested
77
+ if record_metric:
78
+ collector = get_metrics_collector()
79
+ metric = OperationMetric(
80
+ operation=op_name,
81
+ duration_ms=duration_ms,
82
+ success=error is None,
83
+ error=str(error) if error else None,
84
+ )
85
+ collector.record(metric)
86
+
87
+ return wrapper # type: ignore
88
+
89
+ return decorator
90
+
91
+
92
+ def async_timed_operation(
93
+ operation_name: str | None = None, record_metric: bool = True
94
+ ):
95
+ """Async version of timed_operation decorator.
96
+
97
+ Args:
98
+ operation_name: Name for the operation (defaults to function name)
99
+ record_metric: Whether to record the timing as a metric
100
+
101
+ Example:
102
+ @async_timed_operation("fetch_issues")
103
+ async def fetch_issues() -> list[Issue]:
104
+ # ... async implementation
105
+ pass
106
+ """
107
+
108
+ def decorator(func: F) -> F:
109
+ @functools.wraps(func)
110
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
111
+ op_name = operation_name or func.__name__
112
+ start_time = time.perf_counter()
113
+ error: Exception | None = None
114
+ result: Any = None
115
+
116
+ try:
117
+ result = await func(*args, **kwargs)
118
+ return result
119
+ except Exception as e:
120
+ error = e
121
+ raise
122
+ finally:
123
+ # Calculate duration
124
+ duration_ms = (time.perf_counter() - start_time) * 1000
125
+
126
+ # Log the timing
127
+ if error:
128
+ logger.error(
129
+ "async_operation_failed",
130
+ operation=op_name,
131
+ duration_ms=round(duration_ms, 2),
132
+ error=str(error),
133
+ error_type=type(error).__name__,
134
+ )
135
+ else:
136
+ logger.info(
137
+ "async_operation_completed",
138
+ operation=op_name,
139
+ duration_ms=round(duration_ms, 2),
140
+ )
141
+
142
+ # Record metric if requested
143
+ if record_metric:
144
+ collector = get_metrics_collector()
145
+ metric = OperationMetric(
146
+ operation=op_name,
147
+ duration_ms=duration_ms,
148
+ success=error is None,
149
+ error=str(error) if error else None,
150
+ )
151
+ collector.record(metric)
152
+
153
+ return wrapper # type: ignore
154
+
155
+ return decorator
156
+
157
+
158
+ class OperationTimer:
159
+ """Context manager for timing code blocks.
160
+
161
+ Example:
162
+ with OperationTimer("database_query") as timer:
163
+ # ... perform operation
164
+ pass
165
+ print(f"Operation took {timer.duration_ms}ms")
166
+ """
167
+
168
+ def __init__(self, operation_name: str, record_metric: bool = True):
169
+ """Initialize the timer.
170
+
171
+ Args:
172
+ operation_name: Name of the operation being timed
173
+ record_metric: Whether to record the timing as a metric
174
+ """
175
+ self.operation_name = operation_name
176
+ self.record_metric = record_metric
177
+ self.start_time: float | None = None
178
+ self.end_time: float | None = None
179
+ self.duration_ms: float | None = None
180
+ self.error: Exception | None = None
181
+
182
+ def __enter__(self) -> "OperationTimer":
183
+ """Start the timer."""
184
+ self.start_time = time.perf_counter()
185
+ logger.debug("operation_started", operation=self.operation_name)
186
+ return self
187
+
188
+ def __exit__(self, _exc_type, exc_val, _exc_tb) -> None:
189
+ """Stop the timer and record metrics."""
190
+ self.end_time = time.perf_counter()
191
+ assert self.start_time is not None
192
+ self.duration_ms = (self.end_time - self.start_time) * 1000
193
+
194
+ if exc_val:
195
+ self.error = exc_val
196
+ logger.error(
197
+ "operation_failed",
198
+ operation=self.operation_name,
199
+ duration_ms=round(self.duration_ms, 2),
200
+ error=str(exc_val),
201
+ error_type=type(exc_val).__name__,
202
+ )
203
+ else:
204
+ logger.info(
205
+ "operation_completed",
206
+ operation=self.operation_name,
207
+ duration_ms=round(self.duration_ms, 2),
208
+ )
209
+
210
+ # Record metric if requested
211
+ if self.record_metric:
212
+ collector = get_metrics_collector()
213
+ metric = OperationMetric(
214
+ operation=self.operation_name,
215
+ duration_ms=self.duration_ms,
216
+ success=exc_val is None,
217
+ error=str(exc_val) if exc_val else None,
218
+ )
219
+ collector.record(metric)
@@ -0,0 +1,288 @@
1
+ """Performance benchmarking and profiling utilities.
2
+
3
+ This module provides tools for measuring and analyzing performance of CLI operations,
4
+ including execution timing, memory usage, and operation throughput.
5
+ """
6
+
7
+ import time
8
+ from dataclasses import dataclass
9
+ from typing import Any
10
+
11
+ from ..logging import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class OperationProfile:
18
+ """Profile data for a single operation type.
19
+
20
+ Attributes:
21
+ operation: Name of the operation
22
+ count: Number of times operation was executed
23
+ total_time_ms: Total execution time in milliseconds
24
+ min_time_ms: Minimum single execution time
25
+ max_time_ms: Maximum single execution time
26
+ """
27
+
28
+ operation: str
29
+ count: int = 0
30
+ total_time_ms: float = 0.0
31
+ min_time_ms: float = float("inf")
32
+ max_time_ms: float = 0.0
33
+ errors: int = 0
34
+
35
+ @property
36
+ def avg_time_ms(self) -> float:
37
+ """Calculate average execution time."""
38
+ return self.total_time_ms / self.count if self.count > 0 else 0.0
39
+
40
+ @property
41
+ def throughput_per_sec(self) -> float:
42
+ """Calculate operations per second."""
43
+ if self.total_time_ms <= 0:
44
+ return 0.0
45
+ return (self.count / self.total_time_ms) * 1000
46
+
47
+ def record(self, duration_ms: float, error: bool = False) -> None:
48
+ """Record a single operation execution.
49
+
50
+ Args:
51
+ duration_ms: Execution time in milliseconds
52
+ error: Whether the operation failed
53
+ """
54
+ self.count += 1
55
+ self.total_time_ms += duration_ms
56
+ self.min_time_ms = min(self.min_time_ms, duration_ms)
57
+ self.max_time_ms = max(self.max_time_ms, duration_ms)
58
+ if error:
59
+ self.errors += 1
60
+
61
+
62
+ class PerformanceProfiler:
63
+ """Profiles operation performance metrics across a session.
64
+
65
+ Tracks execution time, error rates, and throughput for all operations,
66
+ providing detailed performance insights.
67
+
68
+ Example:
69
+ profiler = PerformanceProfiler()
70
+ profiler.start_operation("fetch_issues")
71
+ # ... do work
72
+ profiler.end_operation("fetch_issues")
73
+
74
+ report = profiler.get_report()
75
+ print(report.format())
76
+ """
77
+
78
+ def __init__(self) -> None:
79
+ """Initialize profiler with no operations."""
80
+ self._profiles: dict[str, OperationProfile] = {}
81
+ self._start_times: dict[str, float] = {}
82
+ logger.debug("profiler_initialized")
83
+
84
+ def start_operation(self, operation: str) -> None:
85
+ """Mark the start of an operation.
86
+
87
+ Args:
88
+ operation: Name of the operation
89
+
90
+ Example:
91
+ profiler.start_operation("sync_all")
92
+ """
93
+ self._start_times[operation] = time.perf_counter()
94
+
95
+ def end_operation(self, operation: str, error: bool = False) -> float:
96
+ """Mark the end of an operation and record metrics.
97
+
98
+ Args:
99
+ operation: Name of the operation
100
+ error: Whether the operation failed
101
+
102
+ Returns:
103
+ Duration in milliseconds
104
+
105
+ Example:
106
+ try:
107
+ # ... do work
108
+ profiler.end_operation("sync_all")
109
+ except Exception:
110
+ profiler.end_operation("sync_all", error=True)
111
+ raise
112
+ """
113
+ if operation not in self._start_times:
114
+ logger.warning("operation_not_started", operation=operation)
115
+ return 0.0
116
+
117
+ start = self._start_times.pop(operation)
118
+ duration_ms = (time.perf_counter() - start) * 1000
119
+
120
+ if operation not in self._profiles:
121
+ self._profiles[operation] = OperationProfile(operation)
122
+
123
+ self._profiles[operation].record(duration_ms, error=error)
124
+ logger.debug(
125
+ "operation_profiled",
126
+ operation=operation,
127
+ duration_ms=duration_ms,
128
+ error=error,
129
+ )
130
+
131
+ return duration_ms
132
+
133
+ def get_profile(self, operation: str) -> OperationProfile | None:
134
+ """Get profile for a specific operation.
135
+
136
+ Args:
137
+ operation: Name of the operation
138
+
139
+ Returns:
140
+ OperationProfile if found, None otherwise
141
+ """
142
+ return self._profiles.get(operation)
143
+
144
+ def get_slowest_operations(self, limit: int = 10) -> list[OperationProfile]:
145
+ """Get the slowest operations by average execution time.
146
+
147
+ Args:
148
+ limit: Maximum number of results to return
149
+
150
+ Returns:
151
+ List of operations sorted by average time (slowest first)
152
+ """
153
+ sorted_ops = sorted(
154
+ self._profiles.values(), key=lambda p: p.avg_time_ms, reverse=True
155
+ )
156
+ return sorted_ops[:limit]
157
+
158
+ def get_report(self) -> "PerformanceReport":
159
+ """Generate a performance report for all operations.
160
+
161
+ Returns:
162
+ PerformanceReport with aggregated metrics
163
+ """
164
+ total_time_ms = sum(p.total_time_ms for p in self._profiles.values())
165
+ total_count = sum(p.count for p in self._profiles.values())
166
+ total_errors = sum(p.errors for p in self._profiles.values())
167
+
168
+ return PerformanceReport(
169
+ profiles=self._profiles.copy(),
170
+ total_time_ms=total_time_ms,
171
+ total_operations=total_count,
172
+ total_errors=total_errors,
173
+ )
174
+
175
+ def clear(self) -> None:
176
+ """Clear all profiles."""
177
+ self._profiles.clear()
178
+ self._start_times.clear()
179
+ logger.debug("profiler_cleared")
180
+
181
+
182
+ @dataclass
183
+ class PerformanceReport:
184
+ """Aggregated performance metrics for a session.
185
+
186
+ Attributes:
187
+ profiles: Dictionary of operation profiles
188
+ total_time_ms: Total execution time
189
+ total_operations: Total operations executed
190
+ total_errors: Total failed operations
191
+ """
192
+
193
+ profiles: dict[str, OperationProfile]
194
+ total_time_ms: float
195
+ total_operations: int
196
+ total_errors: int
197
+
198
+ @property
199
+ def success_rate(self) -> float:
200
+ """Calculate overall success rate (0.0-1.0)."""
201
+ if self.total_operations == 0:
202
+ return 0.0
203
+ return (self.total_operations - self.total_errors) / self.total_operations
204
+
205
+ def format(self) -> str:
206
+ """Format report as human-readable string.
207
+
208
+ Returns:
209
+ Formatted performance report
210
+
211
+ Example:
212
+ report = profiler.get_report()
213
+ print(report.format())
214
+ """
215
+ lines = [
216
+ "═" * 70,
217
+ "PERFORMANCE REPORT",
218
+ "═" * 70,
219
+ f"Total Time: {self.total_time_ms:.2f}ms",
220
+ f"Total Operations: {self.total_operations}",
221
+ f"Success Rate: {self.success_rate:.1%}",
222
+ "",
223
+ "Operation Breakdown:",
224
+ "-" * 70,
225
+ ]
226
+
227
+ # Sort by total time
228
+ sorted_ops = sorted(
229
+ self.profiles.values(), key=lambda p: p.total_time_ms, reverse=True
230
+ )
231
+
232
+ for profile in sorted_ops:
233
+ pct = (
234
+ (profile.total_time_ms / self.total_time_ms * 100)
235
+ if self.total_time_ms > 0
236
+ else 0
237
+ )
238
+ error_str = f" ({profile.errors} errors)" if profile.errors > 0 else ""
239
+
240
+ lines.append(
241
+ f"{profile.operation:30} {profile.count:5} ops "
242
+ f"{profile.avg_time_ms:8.2f}ms avg {pct:5.1f}%{error_str}"
243
+ )
244
+
245
+ lines.append("═" * 70)
246
+ return "\n".join(lines)
247
+
248
+ def get_dict(self) -> dict[str, Any]:
249
+ """Convert report to dictionary format.
250
+
251
+ Returns:
252
+ Dictionary representation of the report
253
+ """
254
+ return {
255
+ "total_time_ms": self.total_time_ms,
256
+ "total_operations": self.total_operations,
257
+ "success_rate": self.success_rate,
258
+ "total_errors": self.total_errors,
259
+ "operations": {
260
+ name: {
261
+ "count": profile.count,
262
+ "total_ms": profile.total_time_ms,
263
+ "avg_ms": profile.avg_time_ms,
264
+ "min_ms": profile.min_time_ms if profile.count > 0 else 0,
265
+ "max_ms": profile.max_time_ms,
266
+ "throughput_ops_per_sec": profile.throughput_per_sec,
267
+ "errors": profile.errors,
268
+ }
269
+ for name, profile in self.profiles.items()
270
+ },
271
+ }
272
+
273
+
274
+ # Global profiler instance
275
+ _profiler = PerformanceProfiler()
276
+
277
+
278
+ def get_profiler() -> PerformanceProfiler:
279
+ """Get the global profiler instance.
280
+
281
+ Returns:
282
+ The singleton PerformanceProfiler instance
283
+
284
+ Example:
285
+ profiler = get_profiler()
286
+ profiler.start_operation("my_op")
287
+ """
288
+ return _profiler