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,286 @@
1
+ """High-level workflow automation for Git integration."""
2
+
3
+ import json
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 Issue, Status
12
+ from roadmap.infrastructure.coordination.core import RoadmapCore
13
+
14
+ from .git import GitCommit, GitIntegration
15
+ from .git_hooks_manager import GitHookManager
16
+
17
+ logger = get_logger()
18
+
19
+
20
+ class WorkflowAutomation:
21
+ """High-level workflow automation orchestrator."""
22
+
23
+ def __init__(self, roadmap_core: RoadmapCore):
24
+ """Initialize WorkflowAutomation.
25
+
26
+ Args:
27
+ roadmap_core: Core roadmap integration instance.
28
+ """
29
+ self.core = roadmap_core
30
+ self.hook_manager = GitHookManager(roadmap_core)
31
+ self.git_integration = GitIntegration()
32
+
33
+ def setup_automation(self, features: list[str] | None = None) -> dict[str, bool]:
34
+ """Set up automated workflow features.
35
+
36
+ Args:
37
+ features: List of features to enable. Options:
38
+ ['git-hooks', 'status-automation', 'progress-tracking']
39
+
40
+ Returns:
41
+ Dict mapping feature names to success status.
42
+ """
43
+ all_features = ["git-hooks", "status-automation", "progress-tracking"]
44
+ features_to_enable = features or all_features
45
+
46
+ results = {}
47
+
48
+ if "git-hooks" in features_to_enable:
49
+ results["git-hooks"] = self.hook_manager.install_hooks()
50
+
51
+ if "status-automation" in features_to_enable:
52
+ results["status-automation"] = self._setup_status_automation()
53
+
54
+ if "progress-tracking" in features_to_enable:
55
+ results["progress-tracking"] = self._setup_progress_tracking()
56
+
57
+ return results
58
+
59
+ def disable_automation(self) -> bool:
60
+ """Disable all workflow automation."""
61
+ try:
62
+ # Remove Git hooks
63
+ self.hook_manager.uninstall_hooks()
64
+
65
+ # Clean up context files
66
+ context_files = [
67
+ ".roadmap_branch_context.json",
68
+ ".roadmap_automation_config.json",
69
+ ".roadmap_progress_tracking.json",
70
+ ]
71
+
72
+ for file_path in context_files:
73
+ if Path(file_path).exists():
74
+ Path(file_path).unlink()
75
+
76
+ return True
77
+
78
+ except Exception as e:
79
+ logger.error(
80
+ "context_cleanup_failed", error=str(e), severity="system_error"
81
+ )
82
+ return False
83
+
84
+ def _setup_status_automation(self) -> bool:
85
+ """Set up intelligent status automation rules."""
86
+ try:
87
+ # Create automation config
88
+ config = {
89
+ "status_rules": {
90
+ "auto_in_progress": True, # Move to in-progress on first commit
91
+ "auto_done_on_close": True, # Mark done on "closes" commit
92
+ "auto_blocked_detection": True, # Detect blocked status from patterns
93
+ },
94
+ "progress_rules": {
95
+ "commit_based_progress": True, # Update progress from commits
96
+ "milestone_auto_complete": True, # Auto-complete milestones
97
+ "velocity_tracking": True, # Track team velocity
98
+ },
99
+ }
100
+
101
+ config_file = Path(".roadmap_automation_config.json")
102
+ config_file.write_text(json.dumps(config, indent=2))
103
+ return True
104
+
105
+ except Exception as e:
106
+ logger.error(
107
+ "status_automation_setup_failed", error=str(e), severity="system_error"
108
+ )
109
+ return False
110
+
111
+ def _setup_progress_tracking(self) -> bool:
112
+ """Set up automated progress tracking."""
113
+ try:
114
+ # Initialize progress tracking state
115
+ tracking_state = {
116
+ "enabled": True,
117
+ "last_sync": datetime.now(UTC).isoformat(),
118
+ "tracked_metrics": [
119
+ "commit_frequency",
120
+ "issue_completion_rate",
121
+ "milestone_progress",
122
+ "team_velocity",
123
+ ],
124
+ }
125
+
126
+ tracking_file = Path(".roadmap_progress_tracking.json")
127
+ tracking_file.write_text(json.dumps(tracking_state, indent=2))
128
+ return True
129
+
130
+ except Exception as e:
131
+ logger.error(
132
+ "progress_tracking_setup_failed", error=str(e), severity="system_error"
133
+ )
134
+ return False
135
+
136
+ def sync_all_issues_with_git(self) -> dict[str, Any]:
137
+ """Sync all issues with their Git activity."""
138
+ results = {"synced_issues": 0, "updated_issues": [], "errors": []}
139
+
140
+ try:
141
+ issues = self.core.issues.list()
142
+ commits = self.git_integration.get_recent_commits(count=100)
143
+
144
+ # Build commit lookup by issue references
145
+ issue_commits = {}
146
+ for commit in commits:
147
+ references = commit.extract_roadmap_references()
148
+ for issue_id in references:
149
+ if issue_id not in issue_commits:
150
+ issue_commits[issue_id] = []
151
+ issue_commits[issue_id].append(commit)
152
+
153
+ # Update issues based on their Git activity
154
+ for issue in issues:
155
+ if issue.id in issue_commits:
156
+ try:
157
+ updated = self._sync_issue_with_commits(
158
+ issue, issue_commits[issue.id]
159
+ )
160
+ if updated:
161
+ results["synced_issues"] += 1
162
+ results["updated_issues"].append(
163
+ {
164
+ "id": issue.id,
165
+ "title": issue.title,
166
+ "commits": len(issue_commits[issue.id]),
167
+ }
168
+ )
169
+ except Exception as e:
170
+ results["errors"].append(f"Issue {issue.id}: {str(e)}")
171
+
172
+ except Exception as e:
173
+ results["errors"].append(f"Sync error: {str(e)}")
174
+
175
+ return results
176
+
177
+ def _sync_issue_with_commits(self, issue: Issue, commits: list[GitCommit]) -> bool:
178
+ """Sync a single issue with its Git commits."""
179
+ updated = False
180
+
181
+ # Sort commits by date
182
+ commits.sort(key=lambda c: c.date)
183
+
184
+ # Initialize git_commits if not present
185
+ if not hasattr(issue, "git_commits"):
186
+ issue.git_commits = []
187
+
188
+ # Process commits and extract progress information
189
+ highest_progress, is_completed = self._process_commits_for_issue(issue, commits)
190
+
191
+ # Update issue status and progress based on extracted data
192
+ updated = self._update_issue_status_and_progress(
193
+ issue, highest_progress, is_completed
194
+ )
195
+
196
+ if updated:
197
+ issue_path = self.core.issues_dir / issue.filename
198
+ IssueParser.save_issue_file(issue, issue_path)
199
+
200
+ return updated
201
+
202
+ def _process_commits_for_issue(
203
+ self, issue: Issue, commits: list[GitCommit]
204
+ ) -> tuple[float | None, bool]:
205
+ """Process commits for an issue and extract progress and completion status.
206
+
207
+ Returns:
208
+ Tuple of (highest_progress_percentage, is_completed_bool)
209
+ """
210
+ highest_progress: float | None = None
211
+ is_completed = False
212
+
213
+ for commit in commits:
214
+ # Check if commit already tracked
215
+ if any(ref.get("hash") == commit.hash for ref in issue.git_commits):
216
+ continue
217
+
218
+ # Extract progress from commit
219
+ progress = commit.extract_progress_info()
220
+ if progress is not None:
221
+ if highest_progress is None or progress > highest_progress:
222
+ highest_progress = progress
223
+
224
+ # Check for completion keywords in commit message
225
+ if self._is_completion_commit(commit):
226
+ is_completed = True
227
+
228
+ # Build and add commit reference
229
+ self._add_commit_reference_to_issue(
230
+ issue, commit, progress, is_completed, commits
231
+ )
232
+
233
+ return highest_progress, is_completed
234
+
235
+ def _is_completion_commit(self, commit: GitCommit) -> bool:
236
+ """Check if commit contains completion keywords."""
237
+ completion_keywords = ["closes roadmap:", "fixes roadmap:"]
238
+ return any(keyword in commit.message.lower() for keyword in completion_keywords)
239
+
240
+ def _add_commit_reference_to_issue(
241
+ self,
242
+ issue: Issue,
243
+ commit: GitCommit,
244
+ progress: float | None,
245
+ is_completed: bool,
246
+ commits: list[GitCommit],
247
+ ):
248
+ """Add a commit reference to the issue's git_commits list."""
249
+ commit_ref = {
250
+ "hash": commit.hash,
251
+ "message": commit.message,
252
+ "date": commit.date.isoformat(),
253
+ "progress": progress,
254
+ "completion": is_completed and commit == commits[-1],
255
+ }
256
+ issue.git_commits.append(commit_ref)
257
+
258
+ def _update_issue_status_and_progress(
259
+ self, issue: Issue, highest_progress: float | None, is_completed: bool
260
+ ) -> bool:
261
+ """Update issue status and progress based on extracted data.
262
+
263
+ Returns:
264
+ True if issue was updated, False otherwise.
265
+ """
266
+ updated = False
267
+
268
+ # Update progress percentage
269
+ if highest_progress is not None:
270
+ issue.progress_percentage = highest_progress
271
+ updated = True
272
+ elif is_completed:
273
+ # If completed without explicit progress, set to 100%
274
+ issue.progress_percentage = 100.0
275
+ updated = True
276
+
277
+ # Update status based on completion and progress
278
+ if is_completed and issue.status != Status.CLOSED:
279
+ issue.status = Status.CLOSED
280
+ issue.completed_date = datetime.now(UTC).isoformat()
281
+ updated = True
282
+ elif highest_progress and highest_progress > 0 and issue.status == Status.TODO:
283
+ issue.status = Status.IN_PROGRESS
284
+ updated = True
285
+
286
+ return updated
@@ -0,0 +1 @@
1
+ """GitHub API adapter and issue operations."""
@@ -0,0 +1,360 @@
1
+ """GitHub API client for roadmap CLI."""
2
+
3
+ import os
4
+ from typing import Any, cast
5
+
6
+ import requests
7
+ from requests.adapters import HTTPAdapter
8
+ from structlog import get_logger
9
+ from urllib3.util.retry import Retry
10
+
11
+ from roadmap.adapters.github.handlers.base import (
12
+ BaseGitHubHandler,
13
+ GitHubAPIError,
14
+ )
15
+ from roadmap.adapters.github.handlers.labels import LabelHandler
16
+ from roadmap.infrastructure.security.credentials import get_credential_manager
17
+
18
+ logger = get_logger()
19
+
20
+
21
+ class GitHubClient(BaseGitHubHandler):
22
+ """GitHub API client for managing issues and milestones."""
23
+
24
+ def __init__(
25
+ self,
26
+ token: str | None = None,
27
+ owner: str | None = None,
28
+ repo: str | None = None,
29
+ ):
30
+ """Initialize GitHub client.
31
+
32
+ Args:
33
+ token: GitHub personal access token
34
+ owner: Repository owner (username or organization)
35
+ repo: Repository name
36
+ """
37
+ # Try to get token from multiple sources with priority:
38
+ # 1. Explicit parameter
39
+ # 2. Environment variable
40
+ # 3. Credential manager
41
+ self.token = token or self._get_token_secure()
42
+ self.owner = owner
43
+ self.repo = repo
44
+
45
+ if not self.token:
46
+ raise GitHubAPIError(
47
+ "GitHub token is required. Set GITHUB_TOKEN environment variable, use credential manager, or provide token."
48
+ )
49
+
50
+ # Set up session with retry strategy
51
+ self.session = requests.Session()
52
+ retry_strategy = Retry(
53
+ total=3,
54
+ status_forcelist=[429, 500, 502, 503, 504],
55
+ allowed_methods=[
56
+ "HEAD",
57
+ "GET",
58
+ "POST",
59
+ "PUT",
60
+ "DELETE",
61
+ "OPTIONS",
62
+ "TRACE",
63
+ ],
64
+ )
65
+ adapter = HTTPAdapter(max_retries=cast(int, retry_strategy))
66
+ self.session.mount("http://", adapter)
67
+ self.session.mount("https://", adapter)
68
+
69
+ # Set default headers
70
+ self.session.headers.update(
71
+ {
72
+ "Authorization": f"token {self.token}",
73
+ "Accept": "application/vnd.github.v3+json",
74
+ "User-Agent": "roadmap-cli/1.0",
75
+ }
76
+ )
77
+
78
+ # Initialize label handler
79
+ self._label_handler = LabelHandler(self.session, owner, repo)
80
+
81
+ # Initialize issue handler
82
+ from roadmap.adapters.github.handlers.issues import IssueHandler
83
+
84
+ self._issue_handler = IssueHandler(self.session, owner, repo)
85
+
86
+ def _get_token_secure(self) -> str | None:
87
+ """Get token from secure sources (environment variable or credential manager)."""
88
+ # First try environment variable
89
+ env_token = os.getenv("GITHUB_TOKEN")
90
+ if env_token:
91
+ return env_token
92
+
93
+ # Then try credential manager
94
+ try:
95
+ credential_manager = get_credential_manager()
96
+ return credential_manager.get_token()
97
+ except Exception as e:
98
+ logger.debug("credential_manager_failed", error=str(e))
99
+ return None
100
+
101
+ def set_repository(self, owner: str, repo: str) -> None:
102
+ """Set the target repository."""
103
+ self.owner = owner
104
+ self.repo = repo
105
+
106
+ def test_authentication(self) -> dict[str, Any]:
107
+ """Test authentication and get user info."""
108
+ response = self._make_request("GET", "/user")
109
+ return response.json()
110
+
111
+ def test_repository_access(self) -> dict[str, Any]:
112
+ """Test repository access."""
113
+ self._check_repository()
114
+ response = self._make_request("GET", f"/repos/{self.owner}/{self.repo}")
115
+ return response.json()
116
+
117
+ def status_to_labels(self, status: str) -> list[str]:
118
+ """Convert issue status to GitHub labels.
119
+
120
+ Args:
121
+ status: The issue status (e.g., 'todo', 'in-progress', 'blocked', 'review', 'closed')
122
+
123
+ Returns:
124
+ List of GitHub labels corresponding to the status
125
+ """
126
+ status_label_map = {
127
+ "todo": ["status:todo"],
128
+ "in-progress": ["status:in-progress"],
129
+ "blocked": ["status:blocked"],
130
+ "review": ["status:review"],
131
+ "closed": ["status:closed"],
132
+ }
133
+ return status_label_map.get(status, [])
134
+
135
+ def labels_to_status(self, labels: list[str]) -> str | None:
136
+ """Convert GitHub labels to issue status.
137
+
138
+ Args:
139
+ labels: List of GitHub labels
140
+
141
+ Returns:
142
+ The corresponding issue status, or None if no status label found
143
+ """
144
+ label_status_map = {
145
+ "status:todo": "todo",
146
+ "status:in-progress": "in-progress",
147
+ "status:blocked": "blocked",
148
+ "status:review": "review",
149
+ "status:closed": "closed",
150
+ }
151
+ for label in labels:
152
+ if label in label_status_map:
153
+ return label_status_map[label]
154
+ return None
155
+
156
+ def get_labels(self) -> list[dict[str, Any]]:
157
+ """Get repository labels.
158
+
159
+ Returns:
160
+ List of label dictionaries
161
+ """
162
+ self._label_handler.owner = self.owner
163
+ self._label_handler.repo = self.repo
164
+ return self._label_handler.get_labels()
165
+
166
+ def create_label(
167
+ self, name: str, color: str, description: str | None = None
168
+ ) -> dict[str, Any]:
169
+ """Create a new label.
170
+
171
+ Args:
172
+ name: Label name
173
+ color: Label color (hex code)
174
+ description: Label description
175
+
176
+ Returns:
177
+ Created label dictionary
178
+ """
179
+ self._label_handler.owner = self.owner
180
+ self._label_handler.repo = self.repo
181
+ return self._label_handler.create_label(name, color, description)
182
+
183
+ def setup_default_labels(self) -> None:
184
+ """Set up default labels for priority and status."""
185
+ self._label_handler.owner = self.owner
186
+ self._label_handler.repo = self.repo
187
+ self._label_handler.setup_default_labels()
188
+
189
+ def get_issues(
190
+ self,
191
+ state: str = "all",
192
+ labels: list[str] | None = None,
193
+ milestone: str | None = None,
194
+ assignee: str | None = None,
195
+ per_page: int = 100,
196
+ ) -> list[dict[str, Any]]:
197
+ """Get issues from the repository.
198
+
199
+ Args:
200
+ state: Issue state ('open', 'closed', 'all')
201
+ labels: Optional list of label names to filter by
202
+ milestone: Optional milestone title to filter by
203
+ assignee: Optional assignee username to filter by
204
+ per_page: Number of issues per page (max 100)
205
+
206
+ Returns:
207
+ List of issue dictionaries
208
+ """
209
+ self._issue_handler.owner = self.owner
210
+ self._issue_handler.repo = self.repo
211
+ return self._issue_handler.get_issues(
212
+ state=state,
213
+ labels=labels,
214
+ milestone=milestone,
215
+ assignee=assignee,
216
+ per_page=per_page,
217
+ )
218
+
219
+ def fetch_issue(self, issue_number: int) -> dict[str, Any]:
220
+ """Fetch a GitHub issue by number.
221
+
222
+ Args:
223
+ issue_number: GitHub issue number (must be positive integer)
224
+
225
+ Returns:
226
+ Dictionary with issue details: number, title, body, state, labels, assignees
227
+
228
+ Raises:
229
+ GitHubAPIError: If issue not found or API error occurs
230
+ ValueError: If issue_number is invalid
231
+ """
232
+ if not isinstance(issue_number, int) or issue_number <= 0:
233
+ raise ValueError(
234
+ f"Issue number must be a positive integer, got {issue_number}"
235
+ )
236
+
237
+ self._check_repository()
238
+ try:
239
+ response = self._make_request(
240
+ "GET", f"/repos/{self.owner}/{self.repo}/issues/{issue_number}"
241
+ )
242
+ data = response.json()
243
+
244
+ # Extract relevant fields
245
+ return {
246
+ "number": data.get("number"),
247
+ "title": data.get("title"),
248
+ "body": data.get("body") or "",
249
+ "state": data.get("state"), # 'open' or 'closed'
250
+ "labels": [label["name"] for label in data.get("labels", [])],
251
+ "assignees": [
252
+ assignee["login"] for assignee in data.get("assignees", [])
253
+ ],
254
+ "milestone": data.get("milestone", {}).get("title")
255
+ if data.get("milestone")
256
+ else None,
257
+ "url": data.get("html_url"),
258
+ "created_at": data.get("created_at"),
259
+ "updated_at": data.get("updated_at"),
260
+ }
261
+ except requests.exceptions.HTTPError as e:
262
+ resp = e.response
263
+ if resp is None:
264
+ raise GitHubAPIError(f"GitHub API error: {e}") from e
265
+ if resp.status_code == 404:
266
+ raise GitHubAPIError(
267
+ f"GitHub issue #{issue_number} not found in {self.owner}/{self.repo}"
268
+ ) from e
269
+ elif resp.status_code == 401:
270
+ raise GitHubAPIError(
271
+ "GitHub authentication failed - invalid token"
272
+ ) from e
273
+ else:
274
+ raise GitHubAPIError(
275
+ f"GitHub API error: {resp.status_code} {resp.reason}"
276
+ ) from e
277
+ except requests.exceptions.RequestException as e:
278
+ raise GitHubAPIError(f"GitHub API connection error: {str(e)}") from e
279
+
280
+ def validate_github_token(self) -> tuple[bool, str]:
281
+ """Validate GitHub token by making a test API call.
282
+
283
+ Returns:
284
+ Tuple of (is_valid, message)
285
+ - (True, "Token is valid") if successful
286
+ - (False, error_message) if invalid or error
287
+ """
288
+ try:
289
+ response = self._make_request("GET", "/user")
290
+ if response.status_code == 200:
291
+ user_data = response.json()
292
+ login = user_data.get("login", "unknown")
293
+ return True, f"Token is valid (authenticated as {login})"
294
+ else:
295
+ return False, f"Token validation failed: {response.status_code}"
296
+ except requests.exceptions.HTTPError as e:
297
+ resp = e.response
298
+ if resp is None:
299
+ return False, f"GitHub API error: {e}"
300
+ if resp.status_code == 401:
301
+ return False, "GitHub token is invalid or expired"
302
+ else:
303
+ return False, f"GitHub API error: {resp.status_code}"
304
+ except requests.exceptions.RequestException as e:
305
+ return False, f"GitHub API connection error: {str(e)}"
306
+
307
+ def validate_assignee(self, assignee: str) -> tuple[bool, str]:
308
+ """Validate that a user is a valid assignee for this repository.
309
+
310
+ Args:
311
+ assignee: GitHub username to validate
312
+
313
+ Returns:
314
+ Tuple of (is_valid, error_message)
315
+ - (True, "") if valid
316
+ - (False, error_message) if invalid
317
+ """
318
+ if not assignee or not assignee.strip():
319
+ return False, "Assignee cannot be empty"
320
+
321
+ assignee = assignee.strip()
322
+
323
+ try:
324
+ self._check_repository()
325
+ # Check if the user exists and has access to the repository
326
+ # We'll try to get the user info from GitHub
327
+ response = self._make_request("GET", f"/users/{assignee}")
328
+
329
+ if response.status_code == 200:
330
+ # User exists, now check if they have access to the repository
331
+ # Try to get their permission level in the repository
332
+ perm_response = self._make_request(
333
+ "GET",
334
+ f"/repos/{self.owner}/{self.repo}/collaborators/{assignee}/permission",
335
+ )
336
+
337
+ if perm_response.status_code == 204:
338
+ # User has some level of access to the repository
339
+ return True, ""
340
+ elif perm_response.status_code == 404:
341
+ # User exists but is not a collaborator - they can still be assigned
342
+ # to public repositories by maintainers, so return True
343
+ return True, ""
344
+ else:
345
+ return False, f"Could not verify access for {assignee}"
346
+ elif response.status_code == 404:
347
+ return False, f"GitHub user '{assignee}' not found"
348
+ else:
349
+ return False, f"GitHub API error: {response.status_code}"
350
+
351
+ except requests.exceptions.HTTPError as e:
352
+ resp = e.response
353
+ if resp is None:
354
+ return False, f"GitHub API error: {e}"
355
+ if resp.status_code == 404:
356
+ return False, f"GitHub user '{assignee}' not found"
357
+ else:
358
+ return False, f"GitHub API error: {resp.status_code}"
359
+ except requests.exceptions.RequestException as e:
360
+ return False, f"GitHub API connection error: {str(e)}"
@@ -0,0 +1,17 @@
1
+ """GitHub API handlers for different resource types."""
2
+
3
+ from roadmap.adapters.github.handlers.base import BaseGitHubHandler
4
+ from roadmap.adapters.github.handlers.collaborators import CollaboratorsHandler
5
+ from roadmap.adapters.github.handlers.comments import CommentsHandler
6
+ from roadmap.adapters.github.handlers.issues import IssueHandler
7
+ from roadmap.adapters.github.handlers.labels import LabelHandler
8
+ from roadmap.adapters.github.handlers.milestones import MilestoneHandler
9
+
10
+ __all__ = [
11
+ "BaseGitHubHandler",
12
+ "IssueHandler",
13
+ "MilestoneHandler",
14
+ "LabelHandler",
15
+ "CommentsHandler",
16
+ "CollaboratorsHandler",
17
+ ]