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,455 @@
1
+ """Git hook installation and management for roadmap integration."""
2
+
3
+ import subprocess
4
+ from datetime import UTC, datetime
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from structlog import get_logger
9
+
10
+ from roadmap.adapters.persistence.parser import IssueParser
11
+ from roadmap.core.domain import MilestoneStatus, Status
12
+ from roadmap.infrastructure.coordination.core import RoadmapCore
13
+
14
+ from .git import GitCommit, GitIntegration
15
+ from .hook_installer import HookInstaller
16
+ from .hook_registry import HookRegistry, HookStatus
17
+
18
+ logger = get_logger()
19
+
20
+
21
+ class GitHookManager:
22
+ """Manages Git hooks for automated roadmap integration."""
23
+
24
+ def __init__(self, roadmap_core: RoadmapCore):
25
+ """Initialize GitHookManager.
26
+
27
+ Args:
28
+ roadmap_core: Core roadmap integration instance.
29
+ """
30
+ self.core = roadmap_core
31
+ self.git_integration = GitIntegration()
32
+ self.hooks_dir = (
33
+ Path(".git/hooks") if self.git_integration.is_git_repository() else None
34
+ )
35
+
36
+ def install_hooks(self, hooks: list[str] | None = None) -> bool:
37
+ """Install roadmap Git hooks.
38
+
39
+ Args:
40
+ hooks: List of hook names to install. If None, installs all available hooks.
41
+
42
+ Returns:
43
+ True if hooks were installed successfully.
44
+ """
45
+ installer = HookInstaller(self.hooks_dir)
46
+ return installer.install(hooks)
47
+
48
+ def uninstall_hooks(self, hooks: list[str] | None = None) -> bool:
49
+ """Remove roadmap Git hooks.
50
+
51
+ Args:
52
+ hooks: List of hook names to uninstall. If None, uninstalls all.
53
+
54
+ Returns:
55
+ True if hooks were uninstalled successfully.
56
+ """
57
+ installer = HookInstaller(self.hooks_dir)
58
+ return installer.uninstall(hooks)
59
+
60
+ def get_hooks_status(self) -> dict[str, dict[str, Any]]:
61
+ """Get the status of all installed roadmap hooks.
62
+
63
+ Returns:
64
+ Dictionary with hook names as keys and status information as values.
65
+ """
66
+ if not self.hooks_dir or not self.hooks_dir.exists():
67
+ return {}
68
+
69
+ status = {}
70
+ for hook_name in HookRegistry.get_available_hooks():
71
+ hook_file = self.hooks_dir / hook_name
72
+ status[hook_name] = HookStatus.get_status_dict(hook_name, hook_file)
73
+
74
+ return status
75
+
76
+ def get_hook_config(self) -> dict[str, Any]:
77
+ """Get the current hook configuration.
78
+
79
+ Returns:
80
+ Dictionary with hook configuration settings.
81
+ """
82
+ return {
83
+ "hooks_directory": str(self.hooks_dir) if self.hooks_dir else None,
84
+ "repository_root": str(Path.cwd()),
85
+ "git_repository": self.git_integration.is_git_repository(),
86
+ "available_hooks": HookRegistry.get_available_hooks(),
87
+ "core_initialized": self.core is not None,
88
+ "hooks_status": self.get_hooks_status(),
89
+ }
90
+
91
+ def handle_post_commit(self):
92
+ """Handle post-commit hook - log commit info and trigger auto-sync.
93
+
94
+ Note: CI tracking (post-1.0 feature) has been moved to future/ci_tracking.py
95
+ """
96
+ try:
97
+ # Get the latest commit SHA for logging
98
+ result = subprocess.run(
99
+ ["git", "rev-parse", "HEAD"], capture_output=True, text=True, check=True
100
+ )
101
+ latest_commit_sha = result.stdout.strip()
102
+
103
+ if not latest_commit_sha:
104
+ return
105
+
106
+ # Log the commit for basic tracking
107
+ self._log_hook_activity("Commit", latest_commit_sha[:7])
108
+
109
+ # Trigger auto-sync if enabled
110
+ self._trigger_auto_sync_on_commit(latest_commit_sha)
111
+
112
+ except Exception as e:
113
+ # Silent fail to avoid breaking Git operations
114
+ logger.error(
115
+ "handle_post_commit_failed", error=str(e), severity="operational"
116
+ )
117
+
118
+ def handle_post_checkout(self):
119
+ """Handle post-checkout hook - track branch changes and trigger auto-sync.
120
+
121
+ Note: Advanced CI branch tracking (post-1.0 feature) has been moved to future/ci_tracking.py
122
+ """
123
+ try:
124
+ # Get current branch
125
+ result = subprocess.run(
126
+ ["git", "branch", "--show-current"],
127
+ capture_output=True,
128
+ text=True,
129
+ check=True,
130
+ )
131
+ branch_name = result.stdout.strip()
132
+
133
+ if not branch_name:
134
+ return
135
+
136
+ # Log the branch checkout for basic tracking
137
+ self._log_hook_activity("Checkout", branch_name)
138
+
139
+ # Trigger auto-sync if enabled
140
+ self._trigger_auto_sync_on_checkout(branch_name)
141
+
142
+ except Exception as e:
143
+ # Silent fail to avoid breaking Git operations
144
+ logger.error(
145
+ "handle_post_checkout_failed", error=str(e), severity="operational"
146
+ )
147
+
148
+ def handle_pre_push(self):
149
+ """Handle pre-push hook - basic push notification and trigger auto-sync.
150
+
151
+ Note: Advanced CI automation (post-1.0 feature) has been moved to future/ci_tracking.py
152
+ """
153
+ try:
154
+ # Get current branch
155
+ result = subprocess.run(
156
+ ["git", "branch", "--show-current"],
157
+ capture_output=True,
158
+ text=True,
159
+ check=True,
160
+ )
161
+ current_branch = result.stdout.strip()
162
+
163
+ if not current_branch:
164
+ return
165
+
166
+ # Log the push for basic tracking
167
+ self._log_hook_activity("Push", current_branch)
168
+
169
+ # Note: We don't auto-sync on pre-push to avoid blocking the push
170
+ # Users can manually sync before pushing if desired
171
+
172
+ except Exception as e:
173
+ # Silent fail to avoid breaking Git operations
174
+ logger.error("handle_pre_push_failed", error=str(e), severity="operational")
175
+
176
+ def handle_post_merge(self):
177
+ """Handle post-merge hook - update milestone progress and trigger auto-sync."""
178
+ try:
179
+ # After a merge, check if any milestones should be updated
180
+ self._update_milestone_progress()
181
+
182
+ # Trigger auto-sync if enabled
183
+ try:
184
+ result = subprocess.run(
185
+ ["git", "rev-parse", "HEAD"],
186
+ capture_output=True,
187
+ text=True,
188
+ check=True,
189
+ )
190
+ merge_commit_sha = result.stdout.strip()
191
+ self._trigger_auto_sync_on_merge(merge_commit_sha)
192
+ except Exception as e:
193
+ logger.debug("trigger_auto_sync_on_merge_failed", error=str(e))
194
+
195
+ except Exception as e:
196
+ logger.error(
197
+ "handle_post_merge_failed", error=str(e), severity="operational"
198
+ )
199
+
200
+ def _log_hook_activity(self, activity_type: str, activity_detail: str) -> None:
201
+ """Log hook activity to .git/roadmap-hooks.log.
202
+
203
+ Args:
204
+ activity_type: Type of activity (Commit, Checkout, Push, etc)
205
+ activity_detail: Details of the activity
206
+ """
207
+ try:
208
+ log_file = Path(".git/roadmap-hooks.log")
209
+ timestamp = datetime.now(UTC).isoformat()
210
+ log_entry = f"{timestamp} - {activity_type}: {activity_detail}\n"
211
+
212
+ with open(log_file, "a") as f:
213
+ f.write(log_entry)
214
+ except Exception as e:
215
+ logger.debug(
216
+ "log_hook_activity_failed", error=str(e)
217
+ ) # Silent fail for logging
218
+
219
+ def _update_issue_from_commit(
220
+ self, issue_id: str, commit: GitCommit, progress: float | None = None
221
+ ):
222
+ """Update an issue based on commit information."""
223
+ try:
224
+ issue = self.core.issues.get(issue_id)
225
+ if not issue:
226
+ return
227
+
228
+ # Update progress if specified
229
+ if progress is not None:
230
+ issue.progress_percentage = min(100.0, max(0.0, progress))
231
+
232
+ # Update status based on commit activity
233
+ if issue.status == Status.TODO and progress and progress > 0:
234
+ issue.status = Status.IN_PROGRESS
235
+
236
+ # Add commit reference to issue history
237
+ if not hasattr(issue, "git_commits"):
238
+ issue.git_commits = []
239
+
240
+ commit_ref = {
241
+ "hash": commit.hash,
242
+ "message": commit.message,
243
+ "date": commit.date.isoformat(),
244
+ "progress": progress,
245
+ }
246
+
247
+ # Avoid duplicates
248
+ if not any(ref["hash"] == commit.hash for ref in issue.git_commits):
249
+ issue.git_commits.append(commit_ref)
250
+
251
+ # Save updated issue
252
+ issue_path = self.core.issues_dir / issue.filename
253
+ IssueParser.save_issue_file(issue, issue_path)
254
+
255
+ except Exception as e:
256
+ logger.error(
257
+ "update_issue_from_commit_failed", error=str(e), severity="operational"
258
+ )
259
+
260
+ def _complete_issue_from_commit(self, issue_id: str, commit: GitCommit):
261
+ """Mark an issue as completed based on commit message."""
262
+ try:
263
+ issue = self.core.issues.get(issue_id)
264
+ if not issue:
265
+ return
266
+
267
+ # Mark as closed if not already
268
+ if issue.status != Status.CLOSED:
269
+ issue.status = Status.CLOSED
270
+ issue.progress_percentage = 100.0
271
+ issue.completed_date = datetime.now(UTC).isoformat()
272
+
273
+ # Add completion commit reference
274
+ if not hasattr(issue, "git_commits"):
275
+ issue.git_commits = []
276
+
277
+ completion_ref = {
278
+ "hash": commit.hash,
279
+ "message": commit.message,
280
+ "date": commit.date.isoformat(),
281
+ "completion": True,
282
+ }
283
+
284
+ issue.git_commits.append(completion_ref)
285
+ issue_path = self.core.issues_dir / issue.filename
286
+ IssueParser.save_issue_file(issue, issue_path)
287
+
288
+ except Exception as e:
289
+ logger.error(
290
+ "complete_issue_from_commit_failed",
291
+ error=str(e),
292
+ severity="operational",
293
+ )
294
+
295
+ def _is_milestone_active(self, milestone) -> bool:
296
+ """Check if milestone is active.
297
+
298
+ Args:
299
+ milestone: Milestone object to check
300
+
301
+ Returns:
302
+ True if milestone status is active
303
+ """
304
+ milestone_status = str(milestone.status).lower() if milestone.status else ""
305
+ return milestone_status == "active"
306
+
307
+ def _get_milestone_issues(self, milestone) -> list:
308
+ """Get all issues in a milestone.
309
+
310
+ Args:
311
+ milestone: Milestone object
312
+
313
+ Returns:
314
+ List of issues in the milestone
315
+ """
316
+ return [
317
+ issue
318
+ for issue in self.core.issues.list()
319
+ if issue.milestone == milestone.name
320
+ ]
321
+
322
+ def _calculate_milestone_progress(self, milestone_issues: list) -> float:
323
+ """Calculate progress percentage for milestone.
324
+
325
+ Args:
326
+ milestone_issues: List of issues in the milestone
327
+
328
+ Returns:
329
+ Progress percentage (0-100)
330
+ """
331
+ if not milestone_issues:
332
+ return 0.0
333
+
334
+ total_issues = len(milestone_issues)
335
+ completed_issues = len(
336
+ [i for i in milestone_issues if i.status == Status.CLOSED]
337
+ )
338
+ return (completed_issues / total_issues) * 100
339
+
340
+ def _update_milestone_attributes(self, milestone, progress: float) -> None:
341
+ """Update milestone attributes based on progress.
342
+
343
+ Args:
344
+ milestone: Milestone object to update
345
+ progress: Progress percentage
346
+ """
347
+ # Update calculated progress
348
+ if hasattr(milestone, "calculated_progress"):
349
+ milestone.calculated_progress = progress
350
+
351
+ # Auto-complete if 100% done
352
+ if progress >= 100 and milestone.status != MilestoneStatus.CLOSED:
353
+ milestone.status = MilestoneStatus.CLOSED
354
+ if hasattr(milestone, "actual_end_date"):
355
+ milestone.actual_end_date = datetime.now(UTC)
356
+
357
+ def _save_milestone(self, milestone) -> None:
358
+ """Save updated milestone to storage.
359
+
360
+ Args:
361
+ milestone: Milestone object to save
362
+ """
363
+ save_method = getattr(self.core, "save_milestone", None)
364
+ if save_method and callable(save_method):
365
+ save_method(milestone)
366
+
367
+ def _update_milestone_progress(self):
368
+ """Update milestone progress based on completed issues."""
369
+ try:
370
+ milestones = self.core.milestones.list()
371
+
372
+ for milestone in milestones:
373
+ if self._is_milestone_active(milestone):
374
+ milestone_issues = self._get_milestone_issues(milestone)
375
+
376
+ if milestone_issues:
377
+ progress = self._calculate_milestone_progress(milestone_issues)
378
+ self._update_milestone_attributes(milestone, progress)
379
+ self._save_milestone(milestone)
380
+
381
+ except Exception as e:
382
+ logger.error(
383
+ "update_milestone_progress_failed", error=str(e), severity="operational"
384
+ )
385
+
386
+ def _trigger_auto_sync_on_commit(self, commit_sha: str | None = None):
387
+ """Trigger auto-sync after commit if enabled.
388
+
389
+ Args:
390
+ commit_sha: Git commit SHA for logging
391
+ """
392
+ self._trigger_auto_sync_operation(
393
+ "auto_sync_on_commit",
394
+ commit_sha=commit_sha,
395
+ confirm=False, # Non-interactive in hooks
396
+ )
397
+
398
+ def _trigger_auto_sync_on_checkout(self, branch: str | None = None):
399
+ """Trigger auto-sync after checkout if enabled.
400
+
401
+ Args:
402
+ branch: Branch name for logging
403
+ """
404
+ self._trigger_auto_sync_operation(
405
+ "auto_sync_on_checkout",
406
+ branch=branch,
407
+ confirm=False, # Non-interactive in hooks
408
+ )
409
+
410
+ def _trigger_auto_sync_on_merge(self, commit_sha: str | None = None):
411
+ """Trigger auto-sync after merge if enabled.
412
+
413
+ Args:
414
+ commit_sha: Merge commit SHA for logging
415
+ """
416
+ self._trigger_auto_sync_operation(
417
+ "auto_sync_on_merge",
418
+ commit_sha=commit_sha,
419
+ confirm=False, # Non-interactive in hooks
420
+ )
421
+
422
+ def _trigger_auto_sync_operation(self, operation: str, **kwargs) -> None:
423
+ """Execute an auto-sync operation with common error handling.
424
+
425
+ Args:
426
+ operation: The name of the auto-sync method to call
427
+ **kwargs: Arguments to pass to the sync method
428
+ """
429
+ try:
430
+ from roadmap.core.services.git.git_hook_auto_sync_service import (
431
+ GitHookAutoSyncService,
432
+ )
433
+
434
+ # Load config
435
+ config_path = Path(".roadmap") / "config.json"
436
+ if not config_path.exists():
437
+ return
438
+
439
+ # Initialize service and load config
440
+ service = GitHookAutoSyncService(self.core)
441
+ if not service.load_config_from_file(config_path):
442
+ return
443
+
444
+ # Trigger sync if enabled (no confirmation in hooks)
445
+ method = getattr(service, operation, None)
446
+ if method:
447
+ method(**kwargs)
448
+
449
+ except Exception as e:
450
+ # Silent fail to avoid breaking Git operations
451
+ logger.error(
452
+ "trigger_auto_sync_operation_failed",
453
+ error=str(e),
454
+ severity="operational",
455
+ )
@@ -0,0 +1,65 @@
1
+ """Git repository information retrieval."""
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from roadmap.adapters.git.git_branch_manager import GitBranchManager
8
+ from roadmap.adapters.git.git_command_executor import GitCommandExecutor
9
+
10
+
11
+ class GitRepositoryInfo:
12
+ """Retrieves Git repository information and user details."""
13
+
14
+ def __init__(self, repo_path: Path | None = None):
15
+ """Initialize repository info manager."""
16
+ self.executor = GitCommandExecutor(repo_path)
17
+ self.branch_manager = GitBranchManager(repo_path)
18
+
19
+ def get_current_user(self) -> str | None:
20
+ """Get current Git user name."""
21
+ return self.executor.run(["config", "user.name"])
22
+
23
+ def get_current_email(self) -> str | None:
24
+ """Get current Git user email."""
25
+ return self.executor.run(["config", "user.email"])
26
+
27
+ def get_repository_info(self) -> dict[str, Any]:
28
+ """Get general repository information.
29
+
30
+ Returns:
31
+ Dictionary with repo metadata: origin_url, github_owner, github_repo,
32
+ current_branch, repo_root, total_commits
33
+ """
34
+ if not self.executor.is_git_repository():
35
+ return {}
36
+
37
+ info = {}
38
+
39
+ # Remote origin URL
40
+ origin_url = self.executor.run(["config", "--get", "remote.origin.url"])
41
+ if origin_url:
42
+ info["origin_url"] = origin_url
43
+
44
+ # Try to extract GitHub repo info
45
+ github_match = re.search(r"github\.com[:/]([^/]+)/([^/.]+)", origin_url)
46
+ if github_match:
47
+ info["github_owner"] = github_match.group(1)
48
+ info["github_repo"] = github_match.group(2)
49
+
50
+ # Current branch
51
+ current_branch = self.branch_manager.get_current_branch()
52
+ if current_branch:
53
+ info["current_branch"] = current_branch.name
54
+
55
+ # Repository root
56
+ repo_root = self.executor.run(["rev-parse", "--show-toplevel"])
57
+ if repo_root:
58
+ info["repo_root"] = repo_root
59
+
60
+ # Total commits
61
+ commit_count = self.executor.run(["rev-list", "--count", "HEAD"])
62
+ if commit_count:
63
+ info["total_commits"] = int(commit_count)
64
+
65
+ return info
@@ -0,0 +1,106 @@
1
+ """Hook installation and removal operations."""
2
+
3
+ from pathlib import Path
4
+
5
+ from structlog import get_logger
6
+
7
+ from .hook_registry import HookRegistry
8
+ from .hook_script_generator import HookContentGenerator
9
+
10
+ logger = get_logger()
11
+
12
+
13
+ class HookInstaller:
14
+ """Handles installation and uninstallation of Git hooks."""
15
+
16
+ def __init__(self, hooks_dir: Path | None):
17
+ """Initialize with Git hooks directory.
18
+
19
+ Args:
20
+ hooks_dir: Path to .git/hooks directory, or None if not in repo
21
+ """
22
+ self.hooks_dir = hooks_dir
23
+
24
+ def can_install(self) -> bool:
25
+ """Check if hooks can be installed in this repository."""
26
+ return self.hooks_dir is not None and self.hooks_dir.exists()
27
+
28
+ def install(self, hooks: list[str] | None = None) -> bool:
29
+ """Install specified hooks.
30
+
31
+ Args:
32
+ hooks: List of hook names to install, or None for all
33
+
34
+ Returns:
35
+ True if successful
36
+ """
37
+ if not self.can_install():
38
+ return False
39
+
40
+ hooks_to_install = HookRegistry.validate_hooks(hooks)
41
+
42
+ try:
43
+ for hook_name in hooks_to_install:
44
+ self._install_single_hook(hook_name)
45
+ return True
46
+ except Exception as e:
47
+ print(f"Error installing hooks: {e}")
48
+ return False
49
+
50
+ def uninstall(self, hooks: list[str] | None = None) -> bool:
51
+ """Uninstall specified hooks (or all if not specified).
52
+
53
+ Args:
54
+ hooks: List of hook names to uninstall, or None for all
55
+
56
+ Returns:
57
+ True if successful
58
+ """
59
+ if not self.hooks_dir:
60
+ return False
61
+
62
+ hooks_to_remove = HookRegistry.validate_hooks(hooks)
63
+
64
+ try:
65
+ for hook_name in hooks_to_remove:
66
+ self._uninstall_single_hook(hook_name)
67
+ return True
68
+ except Exception as e:
69
+ logger.error("hook_uninstall_failed", error=str(e), severity="operational")
70
+ return False
71
+
72
+ def _install_single_hook(self, hook_name: str) -> None:
73
+ """Install a single hook.
74
+
75
+ Args:
76
+ hook_name: Name of the hook to install
77
+ """
78
+ if not self.hooks_dir:
79
+ return
80
+
81
+ hook_file = self.hooks_dir / hook_name
82
+ content = HookContentGenerator.generate(hook_name)
83
+
84
+ # Write hook file
85
+ hook_file.write_text(content)
86
+
87
+ # Make it executable
88
+ hook_file.chmod(0o755)
89
+
90
+ def _uninstall_single_hook(self, hook_name: str) -> None:
91
+ """Uninstall a single hook (if it's a roadmap hook).
92
+
93
+ Args:
94
+ hook_name: Name of the hook to uninstall
95
+ """
96
+ if not self.hooks_dir:
97
+ return
98
+
99
+ hook_file = self.hooks_dir / hook_name
100
+ if not hook_file.exists():
101
+ return
102
+
103
+ # Only remove if it's our hook
104
+ content = hook_file.read_text()
105
+ if "roadmap-hook" in content:
106
+ hook_file.unlink()