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,489 @@
1
+ """Secure credential management for GitHub tokens."""
2
+
3
+ import os
4
+ import platform
5
+ import subprocess
6
+
7
+ from roadmap.common.errors import OperationType, safe_operation
8
+ from roadmap.common.logging import get_logger
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class CredentialManagerError(Exception):
14
+ """Exception raised for credential manager errors."""
15
+
16
+ pass
17
+
18
+
19
+ class CredentialManager:
20
+ """Cross-platform secure credential manager."""
21
+
22
+ SERVICE_NAME = "roadmap-cli"
23
+ ACCOUNT_NAME = "github-token"
24
+
25
+ def __init__(self):
26
+ """Initialize credential manager."""
27
+ self.system = platform.system().lower()
28
+
29
+ @safe_operation(OperationType.SAVE, "Token", include_traceback=True)
30
+ def store_token(self, token: str, repo_info: dict[str, str] | None = None) -> bool:
31
+ """Store GitHub token securely.
32
+
33
+ Args:
34
+ token: GitHub personal access token
35
+ repo_info: Optional repository information (owner, repo)
36
+
37
+ Returns:
38
+ True if stored successfully, False otherwise
39
+
40
+ Raises:
41
+ CredentialManagerError: If token storage fails
42
+ """
43
+ logger.info("storing_github_token")
44
+ try:
45
+ if self.system == "darwin":
46
+ result = self._store_token_keychain(token, repo_info)
47
+ elif self.system == "windows":
48
+ result = self._store_token_wincred(token, repo_info)
49
+ elif self.system == "linux":
50
+ result = self._store_token_secretservice(token, repo_info)
51
+ else:
52
+ # Fallback for unsupported systems
53
+ result = self._store_token_fallback(token, repo_info)
54
+
55
+ logger.info("github_token_stored", system=self.system)
56
+ return result
57
+ except Exception as e:
58
+ logger.error(
59
+ "github_token_store_failed",
60
+ error=str(e),
61
+ system=self.system,
62
+ severity="config_error",
63
+ )
64
+ raise CredentialManagerError(f"Failed to store token: {e}") from e
65
+
66
+ def get_token(self) -> str | None:
67
+ """Retrieve GitHub token securely.
68
+
69
+ Returns:
70
+ GitHub token if found, None otherwise
71
+ """
72
+ try:
73
+ # Always check environment variable first
74
+ env_token = os.getenv("GITHUB_TOKEN")
75
+ if env_token:
76
+ logger.debug("github_token_retrieved_from_env")
77
+ return env_token
78
+
79
+ if self.system == "darwin":
80
+ token = self._get_token_keychain()
81
+ elif self.system == "windows":
82
+ token = self._get_token_wincred()
83
+ elif self.system == "linux":
84
+ token = self._get_token_secretservice()
85
+ else:
86
+ # Fallback for unsupported systems
87
+ token = self._get_token_fallback()
88
+
89
+ if token:
90
+ logger.debug("github_token_retrieved", system=self.system)
91
+ else:
92
+ logger.debug("github_token_not_found", system=self.system)
93
+
94
+ return token
95
+ except Exception as e:
96
+ # Silently fail and return None - credential retrieval should be non-blocking
97
+ logger.warning(
98
+ "github_token_retrieval_failed",
99
+ error=str(e),
100
+ system=self.system,
101
+ severity="operational",
102
+ )
103
+ return None
104
+
105
+ @safe_operation(OperationType.DELETE, "Token")
106
+ def delete_token(self) -> bool:
107
+ """Delete stored GitHub token.
108
+
109
+ Returns:
110
+ True if deleted successfully, False otherwise
111
+
112
+ Raises:
113
+ CredentialManagerError: If token deletion fails
114
+ """
115
+ logger.info("deleting_github_token")
116
+ try:
117
+ if self.system == "darwin":
118
+ result = self._delete_token_keychain()
119
+ elif self.system == "windows":
120
+ result = self._delete_token_wincred()
121
+ elif self.system == "linux":
122
+ result = self._delete_token_secretservice()
123
+ else:
124
+ # Fallback for unsupported systems
125
+ result = self._delete_token_fallback()
126
+
127
+ logger.info("github_token_deleted", system=self.system, success=result)
128
+ return result
129
+ except Exception as e:
130
+ logger.error(
131
+ "github_token_deletion_failed",
132
+ error=str(e),
133
+ system=self.system,
134
+ severity="config_error",
135
+ )
136
+ raise CredentialManagerError(f"Failed to delete token: {e}") from e
137
+
138
+ def is_available(self) -> bool:
139
+ """Check if credential manager is available on this system.
140
+
141
+ Returns:
142
+ True if credential manager is available, False otherwise
143
+ """
144
+ try:
145
+ if self.system == "darwin":
146
+ return self._check_keychain_available()
147
+ elif self.system == "windows":
148
+ return self._check_wincred_available()
149
+ elif self.system == "linux":
150
+ return self._check_secretservice_available()
151
+ else:
152
+ return False
153
+ except Exception as e:
154
+ logger.debug("secretservice_availability_check_failed", error=str(e))
155
+ return False
156
+
157
+ # macOS Keychain implementation
158
+ def _store_token_keychain(
159
+ self, token: str, repo_info: dict[str, str] | None = None
160
+ ) -> bool:
161
+ """Store token in macOS Keychain."""
162
+ cmd = [
163
+ "security",
164
+ "add-generic-password",
165
+ "-s",
166
+ self.SERVICE_NAME,
167
+ "-a",
168
+ self.ACCOUNT_NAME,
169
+ "-w",
170
+ token,
171
+ "-U", # Update if exists
172
+ ]
173
+
174
+ if repo_info:
175
+ # Add repository info as a comment
176
+ comment = f"GitHub token for {repo_info.get('owner', '')}/{repo_info.get('repo', '')}"
177
+ cmd.extend(["-j", comment])
178
+
179
+ result = subprocess.run(cmd, capture_output=True, text=True)
180
+ return result.returncode == 0
181
+
182
+ def _get_token_keychain(self) -> str | None:
183
+ """Get token from macOS Keychain."""
184
+ cmd = [
185
+ "security",
186
+ "find-generic-password",
187
+ "-s",
188
+ self.SERVICE_NAME,
189
+ "-a",
190
+ self.ACCOUNT_NAME,
191
+ "-w", # Output password only
192
+ ]
193
+
194
+ result = subprocess.run(cmd, capture_output=True, text=True)
195
+ if result.returncode == 0:
196
+ return result.stdout.strip()
197
+ return None
198
+
199
+ def _delete_token_keychain(self) -> bool:
200
+ """Delete token from macOS Keychain."""
201
+ cmd = [
202
+ "security",
203
+ "delete-generic-password",
204
+ "-s",
205
+ self.SERVICE_NAME,
206
+ "-a",
207
+ self.ACCOUNT_NAME,
208
+ ]
209
+
210
+ result = subprocess.run(cmd, capture_output=True, text=True)
211
+ return result.returncode == 0
212
+
213
+ def _check_keychain_available(self) -> bool:
214
+ """Check if macOS Keychain is available."""
215
+ try:
216
+ result = subprocess.run(["security", "-h"], capture_output=True, text=True)
217
+ return result.returncode == 0
218
+ except FileNotFoundError as e:
219
+ logger.debug("security_command_not_found", error=str(e))
220
+ return False
221
+
222
+ # Windows Credential Manager implementation
223
+ def _store_token_wincred(
224
+ self, token: str, repo_info: dict[str, str] | None = None
225
+ ) -> bool:
226
+ """Store token in Windows Credential Manager."""
227
+ try:
228
+ import keyring
229
+
230
+ try:
231
+ from keyring.errors import NoKeyringError
232
+ except Exception:
233
+ NoKeyringError = None
234
+
235
+ target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
236
+ if repo_info:
237
+ target_name += (
238
+ f":{repo_info.get('owner', '')}/{repo_info.get('repo', '')}"
239
+ )
240
+
241
+ try:
242
+ keyring.set_password(self.SERVICE_NAME, target_name, token)
243
+ return True
244
+ except Exception as e:
245
+ if NoKeyringError and isinstance(e, NoKeyringError):
246
+ logger.debug("keyring_no_backend_store_wincred", error=str(e))
247
+ return self._store_token_cmdkey(token, repo_info)
248
+ logger.debug("keyring_store_wincred_failed", error=str(e))
249
+ return False
250
+ except ImportError as e:
251
+ logger.debug("keyring_import_failed_keychain", error=str(e))
252
+ # Fallback to cmdkey if keyring not available
253
+ return self._store_token_cmdkey(token, repo_info)
254
+
255
+ def _get_token_wincred(self) -> str | None:
256
+ """Get token from Windows Credential Manager."""
257
+ try:
258
+ import keyring
259
+
260
+ try:
261
+ from keyring.errors import NoKeyringError
262
+ except Exception:
263
+ NoKeyringError = None
264
+
265
+ target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
266
+ try:
267
+ return keyring.get_password(self.SERVICE_NAME, target_name)
268
+ except Exception as e:
269
+ if NoKeyringError and isinstance(e, NoKeyringError):
270
+ logger.debug("keyring_no_backend_get_wincred", error=str(e))
271
+ return self._get_token_cmdkey()
272
+ logger.debug("keyring_get_wincred_failed", error=str(e))
273
+ return None
274
+ except ImportError as e:
275
+ logger.debug("keyring_import_failed_get_wincred", error=str(e))
276
+ # Fallback to cmdkey if keyring not available
277
+ return self._get_token_cmdkey()
278
+
279
+ def _delete_token_wincred(self) -> bool:
280
+ """Delete token from Windows Credential Manager."""
281
+ try:
282
+ import keyring
283
+
284
+ try:
285
+ from keyring.errors import NoKeyringError
286
+ except Exception:
287
+ NoKeyringError = None
288
+
289
+ target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
290
+ try:
291
+ keyring.delete_password(self.SERVICE_NAME, target_name)
292
+ return True
293
+ except Exception as e:
294
+ if NoKeyringError and isinstance(e, NoKeyringError):
295
+ logger.debug("keyring_no_backend_delete_wincred", error=str(e))
296
+ return self._delete_token_cmdkey()
297
+ logger.debug("keyring_delete_wincred_failed", error=str(e))
298
+ return False
299
+ except ImportError as e:
300
+ logger.debug("keyring_import_failed_delete_wincred", error=str(e))
301
+ # Fallback to cmdkey if keyring not available
302
+ return self._delete_token_cmdkey()
303
+ except Exception as e:
304
+ logger.error(
305
+ "wincred_token_deletion_failed", error=str(e), severity="config_error"
306
+ )
307
+ return False
308
+
309
+ def _check_wincred_available(self) -> bool:
310
+ """Check if Windows Credential Manager is available."""
311
+ try:
312
+ return True
313
+ except ImportError as e:
314
+ logger.debug("keyring_import_failed_check_wincred", error=str(e))
315
+ try:
316
+ result = subprocess.run(
317
+ ["cmdkey", "/?"], capture_output=True, text=True
318
+ )
319
+ return result.returncode == 0
320
+ except FileNotFoundError as e:
321
+ logger.debug("cmdkey_not_found", error=str(e))
322
+ return False
323
+
324
+ def _store_token_cmdkey(
325
+ self, token: str, repo_info: dict[str, str] | None = None
326
+ ) -> bool:
327
+ """Store token using Windows cmdkey."""
328
+ target_name = self.SERVICE_NAME
329
+ if repo_info:
330
+ target_name += f":{repo_info.get('owner', '')}/{repo_info.get('repo', '')}"
331
+
332
+ cmd = [
333
+ "cmdkey",
334
+ f"/generic:{target_name}",
335
+ f"/user:{self.ACCOUNT_NAME}",
336
+ f"/pass:{token}",
337
+ ]
338
+ result = subprocess.run(cmd, capture_output=True, text=True)
339
+ return result.returncode == 0
340
+
341
+ def _get_token_cmdkey(self) -> str | None:
342
+ """Get token using Windows cmdkey (limited functionality)."""
343
+ # cmdkey doesn't provide a way to retrieve passwords, only store/delete
344
+ # This is a limitation of the cmdkey approach
345
+ return None
346
+
347
+ def _delete_token_cmdkey(self) -> bool:
348
+ """Delete token using Windows cmdkey."""
349
+ cmd = ["cmdkey", f"/delete:{self.SERVICE_NAME}"]
350
+ result = subprocess.run(cmd, capture_output=True, text=True)
351
+ return result.returncode == 0
352
+
353
+ # Linux Secret Service implementation
354
+ def _store_token_secretservice(
355
+ self, token: str, repo_info: dict[str, str] | None = None
356
+ ) -> bool:
357
+ """Store token in Linux Secret Service."""
358
+ try:
359
+ import keyring
360
+
361
+ try:
362
+ from keyring.errors import NoKeyringError
363
+ except Exception:
364
+ NoKeyringError = None
365
+
366
+ target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
367
+ if repo_info:
368
+ target_name += (
369
+ f":{repo_info.get('owner', '')}/{repo_info.get('repo', '')}"
370
+ )
371
+
372
+ try:
373
+ keyring.set_password(self.SERVICE_NAME, target_name, token)
374
+ return True
375
+ except Exception as e:
376
+ if NoKeyringError and isinstance(e, NoKeyringError):
377
+ logger.debug("keyring_no_backend_store_secretservice", error=str(e))
378
+ return self._store_token_fallback(token, repo_info)
379
+ logger.debug("keyring_store_secretservice_failed", error=str(e))
380
+ return False
381
+ except ImportError as e:
382
+ logger.debug("keyring_import_failed_store_secretservice", error=str(e))
383
+ # Fallback if keyring not available
384
+ return self._store_token_fallback(token, repo_info)
385
+
386
+ def _get_token_secretservice(self) -> str | None:
387
+ """Get token from Linux Secret Service."""
388
+ try:
389
+ import keyring
390
+
391
+ try:
392
+ from keyring.errors import NoKeyringError
393
+ except Exception:
394
+ NoKeyringError = None
395
+
396
+ target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
397
+ try:
398
+ return keyring.get_password(self.SERVICE_NAME, target_name)
399
+ except Exception as e:
400
+ if NoKeyringError and isinstance(e, NoKeyringError):
401
+ logger.debug("keyring_no_backend_get_secretservice", error=str(e))
402
+ return self._get_token_fallback()
403
+ logger.debug("keyring_get_secretservice_failed", error=str(e))
404
+ return None
405
+ except ImportError as e:
406
+ logger.debug("keyring_import_failed_get_secretservice", error=str(e))
407
+ # Fallback if keyring not available
408
+ return self._get_token_fallback()
409
+
410
+ def _delete_token_secretservice(self) -> bool:
411
+ """Delete token from Linux Secret Service."""
412
+ try:
413
+ import keyring
414
+
415
+ try:
416
+ from keyring.errors import NoKeyringError
417
+ except Exception:
418
+ NoKeyringError = None
419
+
420
+ target_name = f"{self.SERVICE_NAME}:{self.ACCOUNT_NAME}"
421
+ try:
422
+ keyring.delete_password(self.SERVICE_NAME, target_name)
423
+ return True
424
+ except Exception as e:
425
+ if NoKeyringError and isinstance(e, NoKeyringError):
426
+ logger.debug(
427
+ "keyring_no_backend_delete_secretservice", error=str(e)
428
+ )
429
+ return self._delete_token_fallback()
430
+ logger.debug("keyring_delete_secretservice_failed", error=str(e))
431
+ return False
432
+ except ImportError as e:
433
+ logger.debug("keyring_import_failed_delete_token", error=str(e))
434
+ # Fallback if keyring not available
435
+ return self._delete_token_fallback()
436
+ except Exception as e:
437
+ logger.debug("delete_token_secretservice_failed", error=str(e))
438
+ return False
439
+
440
+ def _check_secretservice_available(self) -> bool:
441
+ """Check if Linux Secret Service is available."""
442
+ try:
443
+ import keyring
444
+
445
+ # Try to access the keyring to see if it's functional
446
+ keyring.get_keyring()
447
+ return True
448
+ except ImportError as e:
449
+ logger.debug("keyring_import_failed_check_available", error=str(e))
450
+ return False
451
+ except Exception as e:
452
+ logger.debug("check_secretservice_available_failed", error=str(e))
453
+ return False
454
+
455
+ # Fallback implementation for unsupported systems
456
+ def _store_token_fallback(
457
+ self, token: str, repo_info: dict[str, str] | None = None
458
+ ) -> bool:
459
+ """Fallback token storage (warns user to use environment variable)."""
460
+ # Don't actually store - just return False to indicate secure storage unavailable
461
+ return False
462
+
463
+ def _get_token_fallback(self) -> str | None:
464
+ """Fallback token retrieval (environment variable only)."""
465
+ return os.getenv("GITHUB_TOKEN")
466
+
467
+ def _delete_token_fallback(self) -> bool:
468
+ """Fallback token deletion (no-op)."""
469
+ return True
470
+
471
+
472
+ def get_credential_manager() -> CredentialManager:
473
+ """Get a credential manager instance."""
474
+ return CredentialManager()
475
+
476
+
477
+ def mask_token(token: str) -> str:
478
+ """Mask a token for display purposes.
479
+
480
+ Args:
481
+ token: Token to mask
482
+
483
+ Returns:
484
+ Masked token showing only last 4 characters
485
+ """
486
+ if not token or len(token) < 8:
487
+ return "****"
488
+
489
+ return f"****{token[-4:]}"
@@ -0,0 +1,94 @@
1
+ """Gateway to sync adapter layer for core services.
2
+
3
+ This module mediates all core service access to sync adapter implementations,
4
+ ensuring proper layer separation between Core and Adapters layers.
5
+
6
+ All imports from roadmap.adapters.sync are localized to this module.
7
+ Core services import from this gateway instead of directly from adapters.
8
+ """
9
+
10
+ from typing import Any, Literal
11
+
12
+
13
+ class SyncGateway:
14
+ """Gateway for sync adapter operations.
15
+
16
+ Provides a centralized interface for core services to access sync
17
+ backend and orchestration functionality without direct adapter imports.
18
+ """
19
+
20
+ @staticmethod
21
+ def get_sync_backend(
22
+ backend_type: Literal["github", "git"], core: Any, config: Any
23
+ ) -> Any:
24
+ """Get a sync backend for the specified type.
25
+
26
+ Args:
27
+ backend_type: Type of backend (e.g., "github", "git")
28
+ core: Core context object
29
+ config: Configuration for the backend
30
+
31
+ Returns:
32
+ Backend instance or None if not available
33
+ """
34
+ from roadmap.adapters.sync.backend_factory import get_sync_backend
35
+
36
+ return get_sync_backend(backend_type, core, config)
37
+
38
+ @staticmethod
39
+ def create_sync_cache_orchestrator(
40
+ core: Any,
41
+ backend: Any,
42
+ conflict_resolver: Any = None,
43
+ state_comparator: Any = None,
44
+ show_progress: bool = True,
45
+ **kwargs: Any,
46
+ ) -> Any:
47
+ """Create a sync cache orchestrator for three-way merge sync operations.
48
+
49
+ Args:
50
+ core: Core context object
51
+ backend: Sync backend instance
52
+ conflict_resolver: Resolver for sync conflicts (optional)
53
+ state_comparator: Comparator for sync states (optional)
54
+ show_progress: Whether to show progress during sync (default: True)
55
+ **kwargs: Additional keyword arguments passed to orchestrator
56
+
57
+ Returns:
58
+ SyncCacheOrchestrator instance
59
+ """
60
+ from roadmap.adapters.sync.sync_cache_orchestrator import (
61
+ SyncCacheOrchestrator,
62
+ )
63
+
64
+ return SyncCacheOrchestrator(
65
+ core,
66
+ backend,
67
+ conflict_resolver=conflict_resolver,
68
+ state_comparator=state_comparator,
69
+ show_progress=show_progress,
70
+ **kwargs,
71
+ )
72
+
73
+ @staticmethod
74
+ def link_issue_in_database(
75
+ repo: Any, local_id: str, backend: Any, remote_id: str
76
+ ) -> bool:
77
+ """Link an issue in the database for sync tracking.
78
+
79
+ Args:
80
+ repo: Database repository
81
+ local_id: Local issue identifier
82
+ backend: Sync backend
83
+ remote_id: Remote issue identifier
84
+
85
+ Returns:
86
+ True if linking was successful
87
+ """
88
+ from roadmap.adapters.sync.services.sync_linking_service import (
89
+ SyncLinkingService,
90
+ )
91
+
92
+ return SyncLinkingService.link_issue_in_database(
93
+ repo, local_id, backend, remote_id
94
+ )
@@ -0,0 +1,7 @@
1
+ """Validation layer - Data and configuration validation.
2
+
3
+ This module provides:
4
+ - GitHub validators: GitHub API client and assignee validation
5
+ - Validators: Assignee, assignee pool, and milestone consistency validation
6
+ - File enumeration: Directory and file structure validation
7
+ """