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 @@
1
+ """Package initialization file."""
@@ -0,0 +1,277 @@
1
+ """Comment management service for entities (Issues, Projects, Milestones)."""
2
+
3
+ import uuid
4
+ from datetime import UTC, datetime
5
+
6
+ from roadmap.common.errors.exceptions import ValidationError
7
+ from roadmap.common.logging.error_logging import (
8
+ log_validation_error,
9
+ )
10
+ from roadmap.common.observability.instrumentation import traced
11
+ from roadmap.core.domain import Comment
12
+
13
+
14
+ class CommentService:
15
+ """Service for managing comments on entities."""
16
+
17
+ @staticmethod
18
+ def generate_comment_id() -> int:
19
+ """Generate a unique comment ID.
20
+
21
+ Returns:
22
+ A unique comment ID
23
+ """
24
+ uid = uuid.uuid4()
25
+ return int(str(uid.int)[:15])
26
+
27
+ @staticmethod
28
+ @traced("create_comment")
29
+ def create_comment(
30
+ author: str,
31
+ body: str,
32
+ entity_id: str | None = None,
33
+ in_reply_to: int | None = None,
34
+ ) -> Comment:
35
+ """Create a new comment.
36
+
37
+ Args:
38
+ author: Author name/username
39
+ body: Comment body (markdown)
40
+ entity_id: Optional entity ID the comment is on
41
+ in_reply_to: Optional ID of comment this replies to (for threading)
42
+
43
+ Returns:
44
+ New Comment instance
45
+
46
+ Raises:
47
+ ValidationError: If inputs are invalid
48
+ """
49
+ # Validate inputs
50
+ if not author or not author.strip():
51
+ error = ValidationError(
52
+ domain_message="Comment author cannot be empty",
53
+ user_message="Comment author cannot be empty",
54
+ )
55
+ log_validation_error(
56
+ error,
57
+ entity_type="Comment",
58
+ field_name="author",
59
+ proposed_value=author,
60
+ )
61
+ raise error
62
+
63
+ if not body or not body.strip():
64
+ error = ValidationError(
65
+ domain_message="Comment body cannot be empty",
66
+ user_message="Comment body cannot be empty",
67
+ )
68
+ log_validation_error(
69
+ error,
70
+ entity_type="Comment",
71
+ field_name="body",
72
+ proposed_value=body,
73
+ )
74
+ raise error
75
+
76
+ now = datetime.now(UTC)
77
+
78
+ return Comment(
79
+ id=CommentService.generate_comment_id(),
80
+ issue_id=entity_id or "",
81
+ author=author.strip(),
82
+ body=body.strip(),
83
+ created_at=now,
84
+ updated_at=now,
85
+ in_reply_to=in_reply_to,
86
+ )
87
+
88
+ @staticmethod
89
+ @traced("validate_comment_thread")
90
+ @staticmethod
91
+ def _validate_comment_field(comment: Comment) -> list[str]:
92
+ """Validate individual comment fields.
93
+
94
+ Args:
95
+ comment: Comment to validate
96
+
97
+ Returns:
98
+ List of field validation errors
99
+ """
100
+ errors = []
101
+
102
+ if not isinstance(comment.created_at, datetime):
103
+ errors.append(f"Comment {comment.id}: created_at is not a valid datetime")
104
+ if not isinstance(comment.updated_at, datetime):
105
+ errors.append(f"Comment {comment.id}: updated_at is not a valid datetime")
106
+
107
+ if not comment.author or not comment.author.strip():
108
+ errors.append(f"Comment {comment.id}: author cannot be empty")
109
+
110
+ if not comment.body or not comment.body.strip():
111
+ errors.append(f"Comment {comment.id}: body cannot be empty")
112
+
113
+ return errors
114
+
115
+ @staticmethod
116
+ def _validate_duplicate_ids(comments: list[Comment]) -> tuple[list[str], set[str]]:
117
+ """Validate for duplicate comment IDs.
118
+
119
+ Args:
120
+ comments: List of comments to check
121
+
122
+ Returns:
123
+ Tuple of (error_messages, comment_ids_set)
124
+ """
125
+ errors = []
126
+ comment_ids = set()
127
+
128
+ for comment in comments:
129
+ if comment.id in comment_ids:
130
+ errors.append(f"Duplicate comment ID: {comment.id}")
131
+ comment_ids.add(comment.id)
132
+
133
+ return errors, comment_ids
134
+
135
+ @staticmethod
136
+ def _validate_reply_references(
137
+ comments: list[Comment], comment_ids: set[str]
138
+ ) -> list[str]:
139
+ """Validate in_reply_to references point to existing comments.
140
+
141
+ Args:
142
+ comments: List of comments
143
+ comment_ids: Set of known comment IDs
144
+
145
+ Returns:
146
+ List of reference validation errors
147
+ """
148
+ errors = []
149
+
150
+ for comment in comments:
151
+ if (
152
+ comment.in_reply_to is not None
153
+ and comment.in_reply_to not in comment_ids
154
+ ):
155
+ if comment_ids:
156
+ errors.append(
157
+ f"Comment {comment.id}: in_reply_to references non-existent comment {comment.in_reply_to}"
158
+ )
159
+
160
+ return errors
161
+
162
+ @staticmethod
163
+ def _validate_circular_references(comments: list[Comment]) -> list[str]:
164
+ """Validate for circular references in reply chain.
165
+
166
+ Args:
167
+ comments: List of comments
168
+
169
+ Returns:
170
+ List of circular reference errors
171
+ """
172
+ errors = []
173
+
174
+ for comment in comments:
175
+ if comment.in_reply_to is None:
176
+ continue
177
+
178
+ chain = set()
179
+ current_id = comment.in_reply_to
180
+ max_depth = len(comments) + 1
181
+
182
+ while current_id is not None and max_depth > 0:
183
+ if current_id == comment.id:
184
+ errors.append(f"Comment {comment.id}: circular reference detected")
185
+ break
186
+ if current_id in chain:
187
+ break
188
+ chain.add(current_id)
189
+ current_comment = next(
190
+ (c for c in comments if c.id == current_id), None
191
+ )
192
+ current_id = current_comment.in_reply_to if current_comment else None
193
+ max_depth -= 1
194
+
195
+ return errors
196
+
197
+ @staticmethod
198
+ def validate_comment_thread(comments: list[Comment]) -> list[str]:
199
+ """Validate comment threads for errors.
200
+
201
+ Checks for:
202
+ - Duplicate comment IDs
203
+ - Invalid in_reply_to references
204
+ - Circular references
205
+ - Valid datetime fields
206
+
207
+ Args:
208
+ comments: List of comments to validate
209
+
210
+ Returns:
211
+ List of error messages (empty if valid)
212
+ """
213
+ errors = []
214
+
215
+ # Check for duplicate IDs
216
+ dup_errors, comment_ids = CommentService._validate_duplicate_ids(comments)
217
+ errors.extend(dup_errors)
218
+
219
+ # Check field validity
220
+ for comment in comments:
221
+ field_errors = CommentService._validate_comment_field(comment)
222
+ errors.extend(field_errors)
223
+
224
+ # Check reply references
225
+ ref_errors = CommentService._validate_reply_references(comments, comment_ids)
226
+ errors.extend(ref_errors)
227
+
228
+ # Check for circular references
229
+ circular_errors = CommentService._validate_circular_references(comments)
230
+ errors.extend(circular_errors)
231
+
232
+ return errors
233
+
234
+ @staticmethod
235
+ @traced("build_comment_threads")
236
+ def build_comment_threads(
237
+ comments: list[Comment],
238
+ ) -> dict[int | None, list[Comment]]:
239
+ """Build comment thread hierarchy from flat list.
240
+
241
+ Groups comments by their parent (in_reply_to) for display.
242
+
243
+ Args:
244
+ comments: List of comments
245
+
246
+ Returns:
247
+ Dictionary mapping parent comment ID -> list of replies
248
+ """
249
+ threads: dict[int | None, list[Comment]] = {}
250
+
251
+ for comment in comments:
252
+ parent_id = comment.in_reply_to
253
+ if parent_id not in threads:
254
+ threads[parent_id] = []
255
+ threads[parent_id].append(comment)
256
+
257
+ # Sort by created_at within each thread
258
+ for thread in threads.values():
259
+ thread.sort(key=lambda c: c.created_at)
260
+
261
+ return threads
262
+
263
+ @staticmethod
264
+ @traced("format_comment_for_display")
265
+ def format_comment_for_display(comment: Comment, indent: int = 0) -> str:
266
+ """Format a comment for CLI display.
267
+
268
+ Args:
269
+ comment: Comment to format
270
+ indent: Indentation level (for threaded replies)
271
+
272
+ Returns:
273
+ Formatted comment string
274
+ """
275
+ indent_str = " " * indent
276
+ timestamp = comment.created_at.strftime("%Y-%m-%d %H:%M")
277
+ return f"{indent_str}💬 {comment.author} ({timestamp}):\n{indent_str} {comment.body}"
@@ -0,0 +1 @@
1
+ """Package initialization file."""
@@ -0,0 +1,403 @@
1
+ """Auto-sync service for Git hooks - handles GitHub sync on Git events.
2
+
3
+ This service integrates with Git hooks to automatically sync issues with GitHub
4
+ when Git events occur (commits, checkouts, merges). It provides:
5
+ - Post-commit auto-sync with optional user confirmation
6
+ - Configurable auto-sync behavior
7
+ - Sync history tracking via SyncMetadataService
8
+ - Smart conflict resolution
9
+ """
10
+
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ import structlog
15
+
16
+ from roadmap.common.console import get_console
17
+ from roadmap.core.services.github.github_integration_service import (
18
+ GitHubIntegrationService,
19
+ )
20
+ from roadmap.core.services.sync.sync_metadata_service import SyncMetadataService
21
+
22
+ logger = structlog.get_logger()
23
+
24
+
25
+ class GitHookAutoSyncConfig:
26
+ """Configuration for git hook auto-sync behavior."""
27
+
28
+ def __init__(
29
+ self,
30
+ auto_sync_enabled: bool = False,
31
+ sync_on_commit: bool = False,
32
+ sync_on_checkout: bool = False,
33
+ sync_on_merge: bool = False,
34
+ confirm_before_sync: bool = True,
35
+ force_local: bool = False,
36
+ force_github: bool = False,
37
+ ):
38
+ """Initialize auto-sync configuration.
39
+
40
+ Args:
41
+ auto_sync_enabled: Master switch for all auto-sync
42
+ sync_on_commit: Auto-sync after commits
43
+ sync_on_checkout: Auto-sync on branch changes
44
+ sync_on_merge: Auto-sync on merge operations
45
+ confirm_before_sync: Ask user before syncing
46
+ force_local: Resolve conflicts by keeping local changes
47
+ force_github: Resolve conflicts by keeping GitHub changes
48
+ """
49
+ self.auto_sync_enabled = auto_sync_enabled
50
+ self.sync_on_commit = sync_on_commit
51
+ self.sync_on_checkout = sync_on_checkout
52
+ self.sync_on_merge = sync_on_merge
53
+ self.confirm_before_sync = confirm_before_sync
54
+ self.force_local = force_local
55
+ self.force_github = force_github
56
+
57
+ def to_dict(self) -> dict[str, Any]:
58
+ """Convert to dictionary for storage."""
59
+ return {
60
+ "auto_sync_enabled": self.auto_sync_enabled,
61
+ "sync_on_commit": self.sync_on_commit,
62
+ "sync_on_checkout": self.sync_on_checkout,
63
+ "sync_on_merge": self.sync_on_merge,
64
+ "confirm_before_sync": self.confirm_before_sync,
65
+ "force_local": self.force_local,
66
+ "force_github": self.force_github,
67
+ }
68
+
69
+ @classmethod
70
+ def from_dict(cls, data: dict[str, Any]) -> "GitHookAutoSyncConfig":
71
+ """Create from dictionary (loaded from storage)."""
72
+ return cls(
73
+ auto_sync_enabled=data.get("auto_sync_enabled", False),
74
+ sync_on_commit=data.get("sync_on_commit", False),
75
+ sync_on_checkout=data.get("sync_on_checkout", False),
76
+ sync_on_merge=data.get("sync_on_merge", False),
77
+ confirm_before_sync=data.get("confirm_before_sync", True),
78
+ force_local=data.get("force_local", False),
79
+ force_github=data.get("force_github", False),
80
+ )
81
+
82
+
83
+ class GitHookAutoSyncService:
84
+ """Service for handling automatic GitHub sync on Git events."""
85
+
86
+ def __init__(self, core):
87
+ """Initialize auto-sync service.
88
+
89
+ Args:
90
+ core: RoadmapCore instance
91
+ """
92
+ self.core = core
93
+ self.console = get_console()
94
+ self.metadata_service = SyncMetadataService(core)
95
+ self.config = GitHookAutoSyncConfig()
96
+
97
+ def set_config(self, config: GitHookAutoSyncConfig) -> None:
98
+ """Set auto-sync configuration.
99
+
100
+ Args:
101
+ config: GitHookAutoSyncConfig instance
102
+ """
103
+ self.config = config
104
+
105
+ def should_sync_on_event(self, event: str) -> bool:
106
+ """Check if sync should occur for a given event.
107
+
108
+ Args:
109
+ event: Event name ('commit', 'checkout', 'merge')
110
+
111
+ Returns:
112
+ True if auto-sync is enabled and configured for this event
113
+ """
114
+ if not self.config.auto_sync_enabled:
115
+ return False
116
+
117
+ event_map = {
118
+ "commit": self.config.sync_on_commit,
119
+ "checkout": self.config.sync_on_checkout,
120
+ "merge": self.config.sync_on_merge,
121
+ }
122
+
123
+ return event_map.get(event, False)
124
+
125
+ def auto_sync_on_commit(
126
+ self, commit_sha: str | None = None, confirm: bool = True
127
+ ) -> bool:
128
+ """Automatically sync issues on git commit.
129
+
130
+ Args:
131
+ commit_sha: Git commit SHA (optional, for logging)
132
+ confirm: Whether to ask for confirmation
133
+
134
+ Returns:
135
+ True if sync completed successfully
136
+ """
137
+ if not self.should_sync_on_event("commit"):
138
+ return False
139
+
140
+ return self._perform_auto_sync(
141
+ event="commit",
142
+ commit_sha=commit_sha,
143
+ confirm=confirm or self.config.confirm_before_sync,
144
+ )
145
+
146
+ def auto_sync_on_checkout(
147
+ self, branch: str | None = None, confirm: bool = True
148
+ ) -> bool:
149
+ """Automatically sync issues on branch checkout.
150
+
151
+ Args:
152
+ branch: Branch name (optional, for logging)
153
+ confirm: Whether to ask for confirmation
154
+
155
+ Returns:
156
+ True if sync completed successfully
157
+ """
158
+ if not self.should_sync_on_event("checkout"):
159
+ return False
160
+
161
+ return self._perform_auto_sync(
162
+ event="checkout",
163
+ branch=branch,
164
+ confirm=confirm or self.config.confirm_before_sync,
165
+ )
166
+
167
+ def auto_sync_on_merge(
168
+ self, commit_sha: str | None = None, confirm: bool = True
169
+ ) -> bool:
170
+ """Automatically sync issues on merge.
171
+
172
+ Args:
173
+ commit_sha: Merge commit SHA (optional, for logging)
174
+ confirm: Whether to ask for confirmation
175
+
176
+ Returns:
177
+ True if sync completed successfully
178
+ """
179
+ if not self.should_sync_on_event("merge"):
180
+ return False
181
+
182
+ return self._perform_auto_sync(
183
+ event="merge",
184
+ commit_sha=commit_sha,
185
+ confirm=confirm or self.config.confirm_before_sync,
186
+ )
187
+
188
+ def _perform_auto_sync(
189
+ self,
190
+ event: str,
191
+ commit_sha: str | None = None,
192
+ branch: str | None = None,
193
+ confirm: bool = True,
194
+ ) -> bool:
195
+ """Perform auto-sync on git event.
196
+
197
+ Args:
198
+ event: Event type ('commit', 'checkout', 'merge')
199
+ commit_sha: Git commit SHA if applicable
200
+ branch: Branch name if applicable
201
+ confirm: Whether to ask for confirmation
202
+
203
+ Returns:
204
+ True if sync completed successfully
205
+ """
206
+ try:
207
+ roadmap_root = Path.cwd()
208
+
209
+ # Resolve GitHub config
210
+ gh_service = GitHubIntegrationService(
211
+ roadmap_root, roadmap_root / ".github/config.json"
212
+ )
213
+ config_result = gh_service.get_github_config()
214
+
215
+ # Handle both tuple (real code) and dict (mocked code) returns
216
+ if isinstance(config_result, tuple):
217
+ owner, repo, token = config_result
218
+ config = {"owner": owner, "repo": repo, "token": token}
219
+ else:
220
+ config = config_result
221
+
222
+ if not config:
223
+ # GitHub not configured - silently skip
224
+ return False
225
+
226
+ # Get linked issues
227
+ linked_issues = [
228
+ issue
229
+ for issue in self.core.issues.all()
230
+ if getattr(issue, "github_issue", None)
231
+ ]
232
+
233
+ if not linked_issues:
234
+ # No linked issues - nothing to sync
235
+ return False
236
+
237
+ # Display what will be synced (brief, non-intrusive)
238
+ self.console.print(f"[dim]🔄 Auto-syncing issues ({event})...[/dim]")
239
+
240
+ # Get GitHub backend for sync
241
+ from roadmap.infrastructure.sync_gateway import SyncGateway
242
+
243
+ backend = SyncGateway.get_sync_backend("github", self.core, config)
244
+ if not backend:
245
+ self.console.print(
246
+ "[yellow] ⚠️ GitHub backend not available - skipping sync[/yellow]"
247
+ )
248
+ return False
249
+
250
+ # Create cache orchestrator for actual sync (uses three-way merge)
251
+ from roadmap.core.services.sync.sync_conflict_resolver import (
252
+ SyncConflictResolver,
253
+ )
254
+ from roadmap.core.services.sync.sync_state_comparator import (
255
+ SyncStateComparator,
256
+ )
257
+
258
+ orchestrator = SyncGateway.create_sync_cache_orchestrator(
259
+ self.core,
260
+ backend,
261
+ conflict_resolver=SyncConflictResolver(),
262
+ state_comparator=SyncStateComparator(),
263
+ show_progress=False, # Silent mode for hooks
264
+ )
265
+
266
+ # Run sync detection (dry-run first to preview)
267
+ report = orchestrator.sync_all_issues(
268
+ dry_run=True,
269
+ force_local=self.config.force_local,
270
+ force_remote=self.config.force_github,
271
+ )
272
+
273
+ if not report.changes:
274
+ self.console.print("[dim] ✓ No changes to sync[/dim]")
275
+ return True
276
+
277
+ # Handle conflicts
278
+ if report.conflicts_detected > 0:
279
+ if self.config.force_local:
280
+ self.console.print(
281
+ "[dim] ⚠️ Resolving conflicts with --force-local[/dim]"
282
+ )
283
+ elif self.config.force_github:
284
+ self.console.print(
285
+ "[dim] ⚠️ Resolving conflicts with --force-github[/dim]"
286
+ )
287
+ else:
288
+ # Cannot proceed without conflict resolution
289
+ self.console.print(
290
+ "[yellow] ⚠️ Conflicts detected - skipping sync. "
291
+ "Use 'roadmap sync --force-local' or '--force-remote' to resolve.[/yellow]"
292
+ )
293
+ return False
294
+
295
+ # Ask for confirmation if enabled
296
+ if confirm:
297
+ self.console.print(f" • {len(report.changes)} change(s) to apply")
298
+ response = input(" Continue with sync? [y/N]: ").strip().lower() == "y"
299
+ if not response:
300
+ self.console.print("[dim] Sync cancelled[/dim]")
301
+ return False
302
+
303
+ # Apply changes (dry-run=False)
304
+ apply_report = orchestrator.sync_all_issues(
305
+ dry_run=False,
306
+ force_local=self.config.force_local,
307
+ force_remote=self.config.force_github,
308
+ show_progress=False,
309
+ )
310
+
311
+ if apply_report.error:
312
+ self.console.print(f"[red] ✗ Sync failed: {apply_report.error}[/red]")
313
+ return False
314
+
315
+ # Success
316
+ self.console.print("[green] ✓ Synced successfully[/green]")
317
+ return True
318
+
319
+ except Exception as e:
320
+ # Log error but don't interrupt the git operation
321
+ self.console.print(f"[yellow] ⚠️ Auto-sync error: {str(e)}[/yellow]")
322
+ return False
323
+
324
+ def get_config(self) -> GitHookAutoSyncConfig:
325
+ """Get current auto-sync configuration.
326
+
327
+ Returns:
328
+ Current GitHookAutoSyncConfig
329
+ """
330
+ return self.config
331
+
332
+ def get_sync_stats(self) -> dict[str, Any]:
333
+ """Get sync statistics from metadata service.
334
+
335
+ Returns:
336
+ Dictionary with sync statistics
337
+ """
338
+ all_issues = self.core.issues.all()
339
+ linked_issues = [
340
+ issue for issue in all_issues if getattr(issue, "github_issue", None)
341
+ ]
342
+ return self.metadata_service.get_statistics(linked_issues)
343
+
344
+ def load_config_from_file(self, config_path: Path) -> bool:
345
+ """Load auto-sync config from file.
346
+
347
+ Args:
348
+ config_path: Path to config file
349
+
350
+ Returns:
351
+ True if config loaded successfully
352
+ """
353
+ try:
354
+ import json
355
+
356
+ if not config_path.exists():
357
+ return False
358
+
359
+ with open(config_path) as f:
360
+ data = json.load(f)
361
+
362
+ config_data = data.get("auto_sync", {})
363
+ if config_data:
364
+ self.config = GitHookAutoSyncConfig.from_dict(config_data)
365
+ return True
366
+
367
+ return False
368
+ except Exception as e:
369
+ logger.debug("config_load_failed", error=str(e), action="load_config")
370
+ return False
371
+
372
+ def save_config_to_file(self, config_path: Path) -> bool:
373
+ """Save auto-sync config to file.
374
+
375
+ Args:
376
+ config_path: Path to config file
377
+
378
+ Returns:
379
+ True if config saved successfully
380
+ """
381
+ try:
382
+ import json
383
+
384
+ # Ensure parent directory exists
385
+ config_path.parent.mkdir(parents=True, exist_ok=True)
386
+
387
+ # Load existing config or create new
388
+ config_dict = {}
389
+ if config_path.exists():
390
+ with open(config_path) as f:
391
+ config_dict = json.load(f)
392
+
393
+ # Update auto_sync section
394
+ config_dict["auto_sync"] = self.config.to_dict()
395
+
396
+ # Save back to file
397
+ with open(config_path, "w") as f:
398
+ json.dump(config_dict, f, indent=2)
399
+
400
+ return True
401
+ except Exception as e:
402
+ logger.debug("config_save_failed", error=str(e), action="save_config")
403
+ return False
@@ -0,0 +1 @@
1
+ """Package initialization file."""