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,150 @@
1
+ """GitHub Issues handler."""
2
+
3
+ from typing import Any
4
+
5
+ from roadmap.adapters.github.handlers.base import BaseGitHubHandler
6
+
7
+
8
+ class IssueHandler(BaseGitHubHandler):
9
+ """Handler for GitHub Issues API operations."""
10
+
11
+ def get_issues(
12
+ self,
13
+ state: str = "all",
14
+ labels: list[str] | None = None,
15
+ milestone: str | None = None,
16
+ assignee: str | None = None,
17
+ per_page: int = 100,
18
+ ) -> list[dict[str, Any]]:
19
+ """Get issues from the repository, handling pagination.
20
+
21
+ Automatically fetches all pages of issues. Returns complete list
22
+ regardless of total count.
23
+
24
+ Args:
25
+ state: Issue state ('open', 'closed', 'all')
26
+ labels: Optional list of label names to filter by
27
+ milestone: Optional milestone title to filter by
28
+ assignee: Optional assignee username to filter by
29
+ per_page: Items per page (max 100)
30
+
31
+ Returns:
32
+ List of all issue dictionaries across all pages
33
+ """
34
+ from structlog import get_logger
35
+
36
+ logger = get_logger()
37
+ logger.info("issue_handler_get_issues_called", state=state, per_page=per_page)
38
+
39
+ self._check_repository()
40
+
41
+ params = {
42
+ "state": state,
43
+ "sort": "created",
44
+ "direction": "desc",
45
+ }
46
+
47
+ if labels:
48
+ params["labels"] = ",".join(labels)
49
+ if milestone:
50
+ params["milestone"] = milestone
51
+ if assignee:
52
+ params["assignee"] = assignee
53
+
54
+ all_issues = self._paginate_request(
55
+ "GET",
56
+ f"/repos/{self.owner}/{self.repo}/issues",
57
+ params=params,
58
+ per_page=per_page,
59
+ )
60
+
61
+ issues_only = [issue for issue in all_issues if "pull_request" not in issue]
62
+ pr_count = len(all_issues) - len(issues_only)
63
+ if pr_count:
64
+ logger.info(
65
+ "issue_handler_prs_skipped",
66
+ skipped_count=pr_count,
67
+ issues_count=len(issues_only),
68
+ )
69
+
70
+ logger.info(
71
+ "issue_handler_get_issues_complete",
72
+ total_issues=len(issues_only),
73
+ )
74
+ return issues_only
75
+
76
+ def get_issue(self, issue_number: int) -> dict[str, Any]:
77
+ """Get a specific issue by number."""
78
+ self._check_repository()
79
+ response = self._make_request(
80
+ "GET", f"/repos/{self.owner}/{self.repo}/issues/{issue_number}"
81
+ )
82
+ return response.json()
83
+
84
+ def create_issue(
85
+ self,
86
+ title: str,
87
+ body: str | None = None,
88
+ labels: list[str] | None = None,
89
+ assignees: list[str] | None = None,
90
+ milestone: int | None = None,
91
+ ) -> dict[str, Any]:
92
+ """Create a new issue."""
93
+ self._check_repository()
94
+
95
+ data: dict[str, Any] = {"title": title}
96
+
97
+ if body:
98
+ data["body"] = body
99
+ if labels:
100
+ data["labels"] = labels
101
+ if assignees:
102
+ data["assignees"] = assignees
103
+ if milestone:
104
+ data["milestone"] = milestone
105
+
106
+ response = self._make_request(
107
+ "POST", f"/repos/{self.owner}/{self.repo}/issues", json=data
108
+ )
109
+ return response.json()
110
+
111
+ def update_issue(
112
+ self,
113
+ issue_number: int,
114
+ title: str | None = None,
115
+ body: str | None = None,
116
+ state: str | None = None,
117
+ labels: list[str] | None = None,
118
+ assignees: list[str] | None = None,
119
+ milestone: int | None = None,
120
+ ) -> dict[str, Any]:
121
+ """Update an existing issue."""
122
+ self._check_repository()
123
+
124
+ data = {}
125
+
126
+ if title is not None:
127
+ data["title"] = title
128
+ if body is not None:
129
+ data["body"] = body
130
+ if state is not None:
131
+ data["state"] = state
132
+ if labels is not None:
133
+ data["labels"] = labels
134
+ if assignees is not None:
135
+ data["assignees"] = assignees
136
+ if milestone is not None:
137
+ data["milestone"] = milestone
138
+
139
+ response = self._make_request(
140
+ "PATCH", f"/repos/{self.owner}/{self.repo}/issues/{issue_number}", json=data
141
+ )
142
+ return response.json()
143
+
144
+ def close_issue(self, issue_number: int) -> dict[str, Any]:
145
+ """Close an issue."""
146
+ return self.update_issue(issue_number, state="closed")
147
+
148
+ def reopen_issue(self, issue_number: int) -> dict[str, Any]:
149
+ """Reopen a closed issue."""
150
+ return self.update_issue(issue_number, state="open")
@@ -0,0 +1,164 @@
1
+ """GitHub Labels handler."""
2
+
3
+ from typing import Any
4
+
5
+ from structlog import get_logger
6
+
7
+ from roadmap.adapters.github.handlers.base import BaseGitHubHandler
8
+ from roadmap.core.domain import Priority, Status
9
+
10
+ logger = get_logger()
11
+
12
+
13
+ class LabelHandler(BaseGitHubHandler):
14
+ """Handler for GitHub Labels API operations."""
15
+
16
+ def get_labels(self) -> list[dict[str, Any]]:
17
+ """Get repository labels.
18
+
19
+ Handles pagination automatically - returns all labels.
20
+
21
+ Returns:
22
+ List of all repository labels across all pages
23
+ """
24
+ self._check_repository()
25
+ return self._paginate_request("GET", f"/repos/{self.owner}/{self.repo}/labels")
26
+
27
+ def create_label(
28
+ self, name: str, color: str, description: str | None = None
29
+ ) -> dict[str, Any]:
30
+ """Create a new label."""
31
+ self._check_repository()
32
+
33
+ data = {"name": name, "color": color.lstrip("#")} # Remove # if present
34
+
35
+ if description:
36
+ data["description"] = description
37
+
38
+ response = self._make_request(
39
+ "POST", f"/repos/{self.owner}/{self.repo}/labels", json=data
40
+ )
41
+ return response.json()
42
+
43
+ def update_label(
44
+ self,
45
+ label_name: str,
46
+ new_name: str | None = None,
47
+ color: str | None = None,
48
+ description: str | None = None,
49
+ ) -> dict[str, Any]:
50
+ """Update an existing label."""
51
+ self._check_repository()
52
+
53
+ data = {}
54
+
55
+ if new_name is not None:
56
+ data["new_name"] = new_name
57
+ if color is not None:
58
+ data["color"] = color.lstrip("#")
59
+ if description is not None:
60
+ data["description"] = description
61
+
62
+ response = self._make_request(
63
+ "PATCH", f"/repos/{self.owner}/{self.repo}/labels/{label_name}", json=data
64
+ )
65
+ return response.json()
66
+
67
+ def delete_label(self, label_name: str) -> None:
68
+ """Delete a label."""
69
+ self._check_repository()
70
+ self._make_request(
71
+ "DELETE", f"/repos/{self.owner}/{self.repo}/labels/{label_name}"
72
+ )
73
+
74
+ def priority_to_labels(self, priority: Priority) -> list[str]:
75
+ """Convert priority to GitHub labels."""
76
+ priority_labels = {
77
+ Priority.CRITICAL: ["priority:critical"],
78
+ Priority.HIGH: ["priority:high"],
79
+ Priority.MEDIUM: ["priority:medium"],
80
+ Priority.LOW: ["priority:low"],
81
+ }
82
+ return priority_labels.get(priority, [])
83
+
84
+ def status_to_labels(self, status: Status) -> list[str]:
85
+ """Convert status to GitHub labels."""
86
+ status_labels = {
87
+ Status.TODO: ["status:todo"],
88
+ Status.IN_PROGRESS: ["status:in-progress"],
89
+ Status.BLOCKED: ["status:blocked"],
90
+ Status.REVIEW: ["status:review"],
91
+ Status.CLOSED: ["status:done"],
92
+ }
93
+ return status_labels.get(status, [])
94
+
95
+ def labels_to_priority(self, labels: list[str]) -> Priority | None:
96
+ """Extract priority from GitHub labels."""
97
+ label_names = [
98
+ label.get("name", label) if isinstance(label, dict) else label
99
+ for label in labels
100
+ ]
101
+
102
+ for label in label_names:
103
+ if label == "priority:critical":
104
+ return Priority.CRITICAL
105
+ elif label == "priority:high":
106
+ return Priority.HIGH
107
+ elif label == "priority:medium":
108
+ return Priority.MEDIUM
109
+ elif label == "priority:low":
110
+ return Priority.LOW
111
+
112
+ return None
113
+
114
+ def labels_to_status(self, labels: list[str]) -> Status | None:
115
+ """Extract status from GitHub labels."""
116
+ label_names = [
117
+ label.get("name", label) if isinstance(label, dict) else label
118
+ for label in labels
119
+ ]
120
+
121
+ for label in label_names:
122
+ if label == "status:todo":
123
+ return Status.TODO
124
+ elif label == "status:in-progress":
125
+ return Status.IN_PROGRESS
126
+ elif label == "status:blocked":
127
+ return Status.BLOCKED
128
+ elif label == "status:review":
129
+ return Status.REVIEW
130
+ elif label == "status:done":
131
+ return Status.CLOSED
132
+
133
+ return None
134
+
135
+ def setup_default_labels(self) -> None:
136
+ """Set up default labels for priority and status."""
137
+ self._check_repository()
138
+
139
+ # Priority labels
140
+ priority_labels = [
141
+ ("priority:critical", "FF0000", "Critical priority"),
142
+ ("priority:high", "FF9900", "High priority"),
143
+ ("priority:medium", "FFFF00", "Medium priority"),
144
+ ("priority:low", "00FF00", "Low priority"),
145
+ ]
146
+
147
+ # Status labels
148
+ status_labels = [
149
+ ("status:todo", "CCCCCC", "To do"),
150
+ ("status:in-progress", "0366D6", "In progress"),
151
+ ("status:blocked", "D73A49", "Blocked"),
152
+ ("status:review", "A371F7", "In review"),
153
+ ("status:done", "28A745", "Done"),
154
+ ]
155
+
156
+ existing_labels = [label["name"] for label in self.get_labels()]
157
+
158
+ for name, color, description in priority_labels + status_labels:
159
+ if name not in existing_labels:
160
+ try:
161
+ self.create_label(name, color, description)
162
+ except Exception as e:
163
+ # Label might have been created by another process
164
+ logger.debug("create_label_failed", label_name=name, error=str(e))
@@ -0,0 +1,104 @@
1
+ """GitHub Milestones handler."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from roadmap.adapters.github.handlers.base import BaseGitHubHandler
7
+
8
+
9
+ class MilestoneHandler(BaseGitHubHandler):
10
+ """Handler for GitHub Milestones API operations."""
11
+
12
+ def get_milestones(self, state: str = "open") -> list[dict[str, Any]]:
13
+ """Get milestones from the repository.
14
+
15
+ Handles pagination automatically - returns all milestones.
16
+
17
+ Args:
18
+ state: Milestone state ('open', 'closed', 'all')
19
+
20
+ Returns:
21
+ List of all milestones across all pages
22
+ """
23
+ self._check_repository()
24
+
25
+ params = {"state": state}
26
+ return self._paginate_request(
27
+ "GET", f"/repos/{self.owner}/{self.repo}/milestones", params=params
28
+ )
29
+
30
+ def get_milestone(self, milestone_number: int) -> dict[str, Any]:
31
+ """Get a specific milestone by number."""
32
+ self._check_repository()
33
+ response = self._make_request(
34
+ "GET", f"/repos/{self.owner}/{self.repo}/milestones/{milestone_number}"
35
+ )
36
+ return response.json()
37
+
38
+ def create_milestone(
39
+ self,
40
+ title: str,
41
+ description: str | None = None,
42
+ due_date: datetime | None = None,
43
+ state: str = "open",
44
+ ) -> dict[str, Any]:
45
+ """Create a new milestone."""
46
+ self._check_repository()
47
+
48
+ data = {"title": title, "state": state}
49
+
50
+ if description:
51
+ data["description"] = description
52
+ if due_date:
53
+ # GitHub expects ISO 8601 format with timezone (Z for UTC)
54
+ if due_date.tzinfo is None:
55
+ # Assume UTC for naive datetime
56
+ data["due_on"] = due_date.isoformat() + "Z"
57
+ else:
58
+ data["due_on"] = due_date.isoformat()
59
+
60
+ response = self._make_request(
61
+ "POST", f"/repos/{self.owner}/{self.repo}/milestones", json=data
62
+ )
63
+ return response.json()
64
+
65
+ def update_milestone(
66
+ self,
67
+ milestone_number: int,
68
+ title: str | None = None,
69
+ description: str | None = None,
70
+ due_date: datetime | None = None,
71
+ state: str | None = None,
72
+ ) -> dict[str, Any]:
73
+ """Update an existing milestone."""
74
+ self._check_repository()
75
+
76
+ data = {}
77
+
78
+ if title is not None:
79
+ data["title"] = title
80
+ if description is not None:
81
+ data["description"] = description
82
+ if due_date is not None:
83
+ # GitHub expects ISO 8601 format with timezone (Z for UTC)
84
+ if due_date.tzinfo is None:
85
+ # Assume UTC for naive datetime
86
+ data["due_on"] = due_date.isoformat() + "Z"
87
+ else:
88
+ data["due_on"] = due_date.isoformat()
89
+ if state is not None:
90
+ data["state"] = state
91
+
92
+ response = self._make_request(
93
+ "PATCH",
94
+ f"/repos/{self.owner}/{self.repo}/milestones/{milestone_number}",
95
+ json=data,
96
+ )
97
+ return response.json()
98
+
99
+ def delete_milestone(self, milestone_number: int) -> None:
100
+ """Delete a milestone."""
101
+ self._check_repository()
102
+ self._make_request(
103
+ "DELETE", f"/repos/{self.owner}/{self.repo}/milestones/{milestone_number}"
104
+ )
@@ -0,0 +1,35 @@
1
+ """Persistence Layer - Data Serialization and Storage.
2
+
3
+ This layer handles reading and writing data to files, including:
4
+ - YAML frontmatter parsing
5
+ - File locking mechanisms
6
+ - Enhanced persistence with backup/recovery
7
+ - Database infrastructure management
8
+ - File synchronization
9
+
10
+ Modules:
11
+ - database_manager.py: SQLite connection and schema management
12
+ - file_synchronizer.py: File-to-database synchronization
13
+ - parser.py: Issue/Milestone/Project YAML frontmatter parsing
14
+ - persistence.py: Enhanced YAML validation and recovery
15
+ - file_locking.py: File-based locking mechanism
16
+ """
17
+
18
+ from .conflict_resolver import ConflictResolver
19
+ from .database_manager import DatabaseManager
20
+ from .file_synchronizer import FileSynchronizer
21
+ from .parser import FrontmatterParser, IssueParser, MilestoneParser, ProjectParser
22
+ from .persistence import enhanced_persistence
23
+ from .sync_state_tracker import SyncStateTracker
24
+
25
+ __all__ = [
26
+ "ConflictResolver",
27
+ "DatabaseManager",
28
+ "FileSynchronizer",
29
+ "FrontmatterParser",
30
+ "IssueParser",
31
+ "MilestoneParser",
32
+ "ProjectParser",
33
+ "SyncStateTracker",
34
+ "enhanced_persistence",
35
+ ]
@@ -0,0 +1,190 @@
1
+ """Conflict detection and resolution for file synchronization.
2
+
3
+ This module handles detection and management of conflicts that arise during
4
+ file synchronization operations.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from roadmap.common.logging import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class ConflictResolver:
16
+ """Handles detection and resolution of synchronization conflicts."""
17
+
18
+ def __init__(self, data_dir: Path):
19
+ """Initialize the conflict resolver.
20
+
21
+ Args:
22
+ data_dir: Root directory for data files
23
+ """
24
+ self.data_dir = data_dir
25
+
26
+ def detect_conflicts(self) -> list[str]:
27
+ """Detect files with git merge conflict markers.
28
+
29
+ Returns:
30
+ List of file paths containing conflict markers
31
+ """
32
+ conflict_files = []
33
+
34
+ try:
35
+ for file_path in self.data_dir.rglob("*.md"):
36
+ if self._has_conflict_markers(file_path):
37
+ conflict_files.append(str(file_path))
38
+ except Exception as e:
39
+ logger.warning(
40
+ "error_detecting_conflicts",
41
+ error=str(e),
42
+ severity="operational",
43
+ )
44
+
45
+ return conflict_files
46
+
47
+ def _has_conflict_markers(self, file_path: Path) -> bool:
48
+ """Check if a file contains git conflict markers.
49
+
50
+ Args:
51
+ file_path: Path to file to check
52
+
53
+ Returns:
54
+ True if file contains conflict markers
55
+ """
56
+ try:
57
+ with open(file_path, encoding="utf-8") as f:
58
+ content = f.read()
59
+ # Check for standard git conflict markers
60
+ return (
61
+ "<<<<<<< HEAD" in content
62
+ or "<<<<<<<" in content
63
+ or "=======" in content
64
+ or ">>>>>>>" in content
65
+ )
66
+ except Exception as e:
67
+ logger.debug("conflict_marker_check_failed", error=str(e))
68
+ return False
69
+
70
+ def resolve_conflict(self, file_path: Path, resolution: str) -> bool:
71
+ """Resolve a file conflict by removing markers.
72
+
73
+ Args:
74
+ file_path: Path to file with conflict
75
+ resolution: Which version to keep - "ours" or "theirs"
76
+
77
+ Returns:
78
+ True if resolution was successful
79
+ """
80
+ try:
81
+ with open(file_path, encoding="utf-8") as f:
82
+ content = f.read()
83
+
84
+ # Remove conflict markers and keep appropriate version
85
+ resolved = self._remove_conflict_markers(content, resolution)
86
+
87
+ with open(file_path, "w", encoding="utf-8") as f:
88
+ f.write(resolved)
89
+
90
+ logger.info(
91
+ "conflict_resolved",
92
+ filepath=file_path.name,
93
+ resolution=resolution,
94
+ )
95
+ return True
96
+ except Exception as e:
97
+ logger.error(
98
+ "error_resolving_conflict",
99
+ file_path=str(file_path),
100
+ error=str(e),
101
+ severity="operational",
102
+ )
103
+ return False
104
+
105
+ def _remove_conflict_markers(self, content: str, resolution: str) -> str:
106
+ """Remove conflict markers from content, keeping specified version.
107
+
108
+ Args:
109
+ content: File content with conflict markers
110
+ resolution: Which version to keep - "ours" or "theirs"
111
+
112
+ Returns:
113
+ Content with conflict markers removed
114
+ """
115
+ lines = content.split("\n")
116
+ resolved_lines = []
117
+ in_conflict = False
118
+ ours_section = []
119
+ theirs_section = []
120
+ current_section = None
121
+
122
+ for line in lines:
123
+ if line.startswith("<<<<<<< HEAD"):
124
+ in_conflict = True
125
+ current_section = "ours"
126
+ ours_section = []
127
+ theirs_section = []
128
+ elif line.startswith("======="):
129
+ if in_conflict:
130
+ current_section = "theirs"
131
+ elif line.startswith(">>>>>>>"):
132
+ in_conflict = False
133
+ if resolution == "ours":
134
+ resolved_lines.extend(ours_section)
135
+ else:
136
+ resolved_lines.extend(theirs_section)
137
+ ours_section = []
138
+ theirs_section = []
139
+ elif in_conflict:
140
+ if current_section == "ours":
141
+ ours_section.append(line)
142
+ else:
143
+ theirs_section.append(line)
144
+ else:
145
+ resolved_lines.append(line)
146
+
147
+ return "\n".join(resolved_lines)
148
+
149
+ def auto_resolve_conflicts(
150
+ self, sync_state_tracker, resolution: str = "ours"
151
+ ) -> bool:
152
+ """Automatically resolve all detected conflicts.
153
+
154
+ Args:
155
+ sync_state_tracker: SyncStateTracker instance for updating state
156
+ resolution: Which version to keep for all conflicts
157
+
158
+ Returns:
159
+ True if all conflicts resolved successfully
160
+ """
161
+ conflict_files = self.detect_conflicts()
162
+
163
+ if not conflict_files:
164
+ return True
165
+
166
+ all_resolved = True
167
+ for file_path in conflict_files:
168
+ if not self.resolve_conflict(Path(file_path), resolution):
169
+ all_resolved = False
170
+
171
+ if all_resolved:
172
+ sync_state_tracker.clear_conflicts()
173
+ else:
174
+ sync_state_tracker.mark_conflicts_detected(conflict_files)
175
+
176
+ return all_resolved
177
+
178
+ def get_conflict_summary(self) -> dict[str, Any]:
179
+ """Get summary of detected conflicts.
180
+
181
+ Returns:
182
+ Dictionary with conflict information
183
+ """
184
+ conflict_files = self.detect_conflicts()
185
+
186
+ return {
187
+ "has_conflicts": len(conflict_files) > 0,
188
+ "conflict_count": len(conflict_files),
189
+ "files": conflict_files,
190
+ }