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,182 @@
1
+ """Initialization workflow service for roadmap CLI.
2
+
3
+ Orchestrates the core initialization workflow steps.
4
+ """
5
+
6
+ import shutil
7
+ from pathlib import Path
8
+
9
+ import structlog
10
+
11
+ from roadmap.common.console import get_console
12
+ from roadmap.common.errors.exceptions import OperationError
13
+ from roadmap.common.security import create_secure_directory
14
+ from roadmap.infrastructure.coordination.core import RoadmapCore
15
+
16
+ from .utils import InitializationManifest
17
+
18
+ logger = structlog.get_logger()
19
+
20
+ console = get_console()
21
+
22
+
23
+ class InitializationWorkflow:
24
+ """Orchestrates the initialization workflow steps."""
25
+
26
+ def __init__(self, core: RoadmapCore):
27
+ """Initialize InitializationWorkflow.
28
+
29
+ Args:
30
+ core: Core roadmap instance.
31
+ """
32
+ self.core = core
33
+
34
+ def cleanup_existing(self) -> bool:
35
+ """Remove existing roadmap directory.
36
+
37
+ Returns:
38
+ True if successful, False otherwise
39
+
40
+ Raises:
41
+ OperationError: If removal fails
42
+ """
43
+ try:
44
+ if self.core.roadmap_dir.exists():
45
+ shutil.rmtree(self.core.roadmap_dir)
46
+ return True
47
+ except Exception as e:
48
+ raise OperationError(
49
+ operation="remove existing roadmap", reason=str(e)
50
+ ) from e
51
+
52
+ def create_structure(self) -> None:
53
+ """Create the basic roadmap structure."""
54
+ self.core.initialize()
55
+
56
+ def create_structure_preserve_data(self) -> bool:
57
+ """Create roadmap structure while preserving existing data.
58
+
59
+ If roadmap already exists, creates only missing directories and templates.
60
+ Returns True if successful, raises OperationError otherwise.
61
+
62
+ Raises:
63
+ OperationError: If creation fails
64
+ """
65
+ try:
66
+ roadmap_dir = self.core.roadmap_dir
67
+
68
+ # If directory doesn't exist, just do normal init
69
+ if not roadmap_dir.exists():
70
+ self.core.initialize()
71
+ return True
72
+
73
+ # Roadmap exists - create only missing parts
74
+ # Ensure subdirectories exist
75
+ for subdir in [
76
+ self.core.issues_dir,
77
+ self.core.milestones_dir,
78
+ self.core.projects_dir,
79
+ self.core.templates_dir,
80
+ self.core.artifacts_dir,
81
+ ]:
82
+ create_secure_directory(subdir, 0o755)
83
+
84
+ # Create templates only if missing
85
+ self._create_missing_templates()
86
+
87
+ # Update .gitignore (safe to call multiple times)
88
+ self.core._init_manager._update_gitignore()
89
+
90
+ return True
91
+ except Exception as e:
92
+ raise OperationError(
93
+ operation="create roadmap structure", reason=str(e)
94
+ ) from e
95
+
96
+ def _create_missing_templates(self) -> None:
97
+ """Create template files only if they don't exist."""
98
+ templates_dir = self.core.templates_dir
99
+
100
+ # Check if templates already exist
101
+ if list(templates_dir.glob("*.md")):
102
+ return # Templates already exist, skip
103
+
104
+ # Otherwise create them via initialization manager
105
+ self.core._init_manager._create_default_templates()
106
+
107
+ def generate_config_file(self, user_name: str | None = None) -> None:
108
+ """Generate the config file with user information.
109
+
110
+ Args:
111
+ user_name: User name to store in config, auto-detected if None
112
+ """
113
+ from roadmap.common.configuration import ConfigManager
114
+
115
+ # Auto-detect user if not provided
116
+ if not user_name:
117
+ user_name = ConfigManager.auto_detect_user()
118
+ if not user_name:
119
+ user_name = "unknown"
120
+
121
+ # Auto-detect GitHub info
122
+ github_owner = ConfigManager.auto_detect_github_username()
123
+
124
+ # Create default config
125
+ config = ConfigManager.create_default_config(
126
+ user_name=user_name,
127
+ github_owner=github_owner,
128
+ github_repo=None, # User would configure repo separately
129
+ github_enabled=False, # Disabled by default
130
+ )
131
+
132
+ # Save config
133
+ manager = ConfigManager(self.core.config_file)
134
+ manager.save(config)
135
+
136
+ def ensure_gitignore_entry(self) -> None:
137
+ """Ensure config.yaml is in .gitignore to prevent accidental commits."""
138
+ gitignore_path = Path.cwd() / ".gitignore"
139
+ config_entry = ".roadmap/config.yaml"
140
+ config_entry_with_comment = (
141
+ ".roadmap/config.yaml # Local user configuration (not to be shared)"
142
+ )
143
+
144
+ # If .gitignore doesn't exist, create it
145
+ if not gitignore_path.exists():
146
+ gitignore_path.write_text(
147
+ f"{config_entry_with_comment}\n.env.local\n.env.*.local\n"
148
+ )
149
+ return
150
+
151
+ # Read existing .gitignore
152
+ content = gitignore_path.read_text()
153
+
154
+ # Check if entry already exists (with or without comment)
155
+ if config_entry in content:
156
+ return # Already there
157
+
158
+ # Append entry to .gitignore
159
+ if not content.endswith("\n"):
160
+ content += "\n"
161
+
162
+ content += f"\n{config_entry_with_comment}\n"
163
+ gitignore_path.write_text(content)
164
+
165
+ def record_created_paths(self, manifest: InitializationManifest) -> None:
166
+ """Record all created paths in the manifest."""
167
+ manifest.add_path(self.core.roadmap_dir)
168
+ manifest.add_path(self.core.projects_dir)
169
+ manifest.add_path(self.core.templates_dir)
170
+ manifest.add_path(self.core.config_file)
171
+
172
+ def rollback_on_error(self) -> None:
173
+ """Remove created roadmap directory on error."""
174
+ if self.core.roadmap_dir.exists():
175
+ try:
176
+ shutil.rmtree(self.core.roadmap_dir)
177
+ except Exception as e:
178
+ logger.debug(
179
+ "roadmap_dir_cleanup_failed",
180
+ error=str(e),
181
+ action="cleanup_roadmap_dir",
182
+ )
@@ -0,0 +1,111 @@
1
+ """Initialization service for roadmap CLI.
2
+
3
+ This module handles project initialization workflow including
4
+ project detection, creation, GitHub setup, and validation.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from roadmap.common.logging import get_logger
11
+ from roadmap.common.logging.error_logging import (
12
+ log_error_with_context,
13
+ )
14
+ from roadmap.common.observability.instrumentation import traced
15
+ from roadmap.core.services.initialization import (
16
+ InitializationValidator,
17
+ InitializationWorkflow,
18
+ )
19
+ from roadmap.core.services.project_init.detection import ProjectDetectionService
20
+ from roadmap.infrastructure.coordination.core import RoadmapCore
21
+
22
+ logger = get_logger(__name__)
23
+
24
+
25
+ class ProjectInitializationService:
26
+ """Orchestrates project initialization workflow."""
27
+
28
+ def __init__(self, core: RoadmapCore):
29
+ """Initialize the service.
30
+
31
+ Args:
32
+ core: RoadmapCore instance for this initialization
33
+ """
34
+ self.core = core
35
+ self.workflow = InitializationWorkflow(core)
36
+
37
+ @traced("validate_prerequisites")
38
+ def validate_prerequisites(self, force: bool = False) -> tuple[bool, str]:
39
+ """Validate initialization prerequisites.
40
+
41
+ Args:
42
+ force: Whether force re-initialization is requested
43
+
44
+ Returns:
45
+ Tuple of (is_valid, error_message)
46
+ """
47
+ lock_path = Path.cwd() / ".roadmap_init.lock"
48
+ is_valid, error_msg = InitializationValidator.validate_lockfile(lock_path)
49
+ if not is_valid:
50
+ return is_valid, error_msg or ""
51
+
52
+ is_valid, error_msg = InitializationValidator.check_existing_roadmap(
53
+ self.core, force
54
+ )
55
+ return is_valid, error_msg or ""
56
+
57
+ @traced("handle_force_reinitialization")
58
+ def handle_force_reinitialization(self) -> bool:
59
+ """Handle force re-initialization when roadmap already exists.
60
+
61
+ Returns:
62
+ True if successful
63
+ """
64
+ try:
65
+ return self.workflow.cleanup_existing()
66
+ except Exception as e:
67
+ log_error_with_context(
68
+ e,
69
+ operation="cleanup_existing",
70
+ entity_type="Roadmap",
71
+ )
72
+ logger.error(
73
+ "Failed to cleanup existing roadmap",
74
+ error=str(e),
75
+ severity="operational",
76
+ )
77
+ return False
78
+
79
+ @traced("detect_existing_projects")
80
+ def detect_existing_projects(self) -> list[dict[str, Any]]:
81
+ """Detect existing projects in the roadmap.
82
+
83
+ Returns:
84
+ List of project info dictionaries
85
+ """
86
+ projects_dir = self.core.roadmap_dir / "projects"
87
+ return ProjectDetectionService.detect_existing_projects(projects_dir)
88
+
89
+ @traced("validate_finalization")
90
+ def validate_finalization(self, project_info: dict[str, Any] | None = None) -> bool:
91
+ """Validate the initialized roadmap and finalize.
92
+
93
+ Args:
94
+ project_info: Project info dict if project was created
95
+
96
+ Returns:
97
+ True if validation passes
98
+ """
99
+ try:
100
+ return InitializationValidator.post_init_validate(
101
+ self.core, self.core.roadmap_dir.name, project_info
102
+ )
103
+ except Exception as e:
104
+ log_error_with_context(
105
+ e,
106
+ operation="post_init_validate",
107
+ entity_type="Roadmap",
108
+ additional_context={"project_info": project_info},
109
+ )
110
+ logger.error("Validation failed", error=str(e), severity="operational")
111
+ return False
@@ -0,0 +1 @@
1
+ """Package initialization file."""
@@ -0,0 +1,312 @@
1
+ """Assignee validation strategies for RoadmapCore."""
2
+
3
+ from typing import Any, Protocol
4
+
5
+ from roadmap.common.logging.error_logging import (
6
+ log_error_with_context,
7
+ log_validation_error,
8
+ )
9
+ from roadmap.common.observability.instrumentation import traced
10
+
11
+
12
+ class AssigneeValidationResult:
13
+ """Result of assignee validation."""
14
+
15
+ def __init__(self, is_valid: bool, message: str = "", canonical_id: str = ""):
16
+ """Initialize validation result.
17
+
18
+ Args:
19
+ is_valid: Whether the assignee is valid
20
+ message: Error message if invalid, or warning message
21
+ canonical_id: Canonical ID if valid
22
+ """
23
+ self.is_valid = is_valid
24
+ self.message = message
25
+ self.canonical_id = canonical_id
26
+
27
+
28
+ class GitHubBackend(Protocol):
29
+ """Protocol for GitHub-based validation backends.
30
+
31
+ Abstracts GitHub validation away from core logic.
32
+ """
33
+
34
+ def validate(self, assignee: str) -> tuple[bool, str, str]:
35
+ """Validate assignee against GitHub.
36
+
37
+ Args:
38
+ assignee: Username to validate
39
+
40
+ Returns:
41
+ Tuple of (is_valid, error_message, canonical_id)
42
+ """
43
+ ...
44
+
45
+
46
+ class IdentitySystemValidator:
47
+ """Validates assignees using the identity management system."""
48
+
49
+ def __init__(self, root_path: str | Any):
50
+ """Initialize with roadmap root path.
51
+
52
+ Args:
53
+ root_path: Path to roadmap root directory
54
+ """
55
+ self.root_path = root_path
56
+
57
+ def validate(self, assignee: str) -> AssigneeValidationResult:
58
+ """Validate assignee using identity management system.
59
+
60
+ Args:
61
+ assignee: Username to validate
62
+
63
+ Returns:
64
+ Validation result with canonical ID if successful
65
+ """
66
+ # Identity system not available - return special marker
67
+ return AssigneeValidationResult(
68
+ is_valid=False, message="", canonical_id="identity-unavailable"
69
+ )
70
+
71
+ def get_validation_mode(self) -> str:
72
+ """Get the configured validation mode.
73
+
74
+ Returns:
75
+ Validation mode string (e.g., 'hybrid', 'local-only', 'github-only')
76
+ """
77
+ return "local-only"
78
+
79
+
80
+ class LocalValidator:
81
+ """Validates assignees using basic local rules."""
82
+
83
+ @traced("validate_assignee_local")
84
+ def validate(self, assignee: str) -> AssigneeValidationResult:
85
+ """Validate assignee using basic local rules.
86
+
87
+ Args:
88
+ assignee: Username to validate
89
+
90
+ Returns:
91
+ Validation result
92
+ """
93
+ # Basic validation for local usage
94
+ if len(assignee) >= 2 and not any(char in assignee for char in "<>{}[]()"):
95
+ return AssigneeValidationResult(is_valid=True, canonical_id=assignee)
96
+ else:
97
+ error = ValueError(f"'{assignee}' is not a valid assignee name")
98
+ log_validation_error(
99
+ error,
100
+ entity_type="Assignee",
101
+ field_name="assignee",
102
+ proposed_value=assignee,
103
+ )
104
+ return AssigneeValidationResult(
105
+ is_valid=False, message=f"'{assignee}' is not a valid assignee name"
106
+ )
107
+
108
+
109
+ class AssigneeValidationStrategy:
110
+ """Orchestrates assignee validation using multiple strategies."""
111
+
112
+ def __init__(
113
+ self,
114
+ root_path: str | Any,
115
+ github_config: tuple[str, str, str] | None = None,
116
+ cached_members: set[str] | list[str] | None = None,
117
+ ):
118
+ """Initialize validation strategy.
119
+
120
+ Args:
121
+ root_path: Path to roadmap root directory
122
+ github_config: Optional tuple of (token, owner, repo) for GitHub validation
123
+ cached_members: Optional cached team members for performance
124
+ """
125
+ self.root_path = root_path
126
+ self.github_config = github_config
127
+ self.cached_members = cached_members
128
+
129
+ self.identity_validator = IdentitySystemValidator(root_path)
130
+ self.local_validator = LocalValidator()
131
+
132
+ def _validate_empty(self, assignee: str) -> tuple[bool, str, str] | None:
133
+ """Check if assignee is empty."""
134
+ if not assignee or not assignee.strip():
135
+ return False, "Assignee cannot be empty", ""
136
+ return None
137
+
138
+ def _try_identity_validation(self, assignee: str) -> tuple[bool, str, str] | None:
139
+ """Try validating with identity system."""
140
+ identity_result = self.identity_validator.validate(assignee)
141
+ if identity_result.is_valid:
142
+ return True, "", identity_result.canonical_id
143
+ return None
144
+
145
+ def _try_identity_unavailable_path(
146
+ self, assignee: str, identity_result
147
+ ) -> tuple[bool, str, str] | None:
148
+ """Handle case where identity system is unavailable."""
149
+ if identity_result.canonical_id != "identity-unavailable":
150
+ return None
151
+
152
+ if self.github_config:
153
+ token, owner, repo = self.github_config
154
+ if token and owner and repo:
155
+ github_result = self._validate_with_github(assignee)
156
+ if github_result.is_valid:
157
+ return True, "", github_result.canonical_id
158
+ else:
159
+ return False, github_result.message, ""
160
+
161
+ local_result = self.local_validator.validate(assignee)
162
+ if local_result.is_valid:
163
+ return True, "", local_result.canonical_id
164
+ else:
165
+ return False, local_result.message, ""
166
+
167
+ def _try_identity_failed_path(self, assignee: str, validation_mode: str):
168
+ """Try fallback validation when identity system failed."""
169
+ if self._should_use_github_validation(validation_mode):
170
+ github_result = self._validate_with_github(assignee)
171
+ if github_result.is_valid:
172
+ return True, "", github_result.canonical_id
173
+ elif validation_mode == "github-only":
174
+ return False, github_result.message, ""
175
+
176
+ if self._should_use_local_validation(validation_mode):
177
+ local_result = self.local_validator.validate(assignee)
178
+ if local_result.is_valid:
179
+ return True, "", local_result.canonical_id
180
+ else:
181
+ return False, local_result.message, ""
182
+
183
+ return None
184
+
185
+ @traced("validate_assignee_strategy")
186
+ def validate(self, assignee: str) -> tuple[bool, str, str]:
187
+ """Validate assignee using appropriate strategy.
188
+
189
+ Args:
190
+ assignee: Username to validate
191
+
192
+ Returns:
193
+ Tuple of (is_valid, error_message, canonical_id)
194
+ """
195
+ try:
196
+ # Check for empty assignee
197
+ empty_result = self._validate_empty(assignee)
198
+ if empty_result is not None:
199
+ is_valid, message, _ = empty_result
200
+ if not is_valid:
201
+ log_validation_error(
202
+ ValueError(message),
203
+ entity_type="Assignee",
204
+ field_name="assignee",
205
+ proposed_value=assignee,
206
+ )
207
+ return empty_result
208
+
209
+ assignee = assignee.strip()
210
+
211
+ # Try identity system first
212
+ identity_result = self.identity_validator.validate(assignee)
213
+ identity_valid = self._try_identity_validation(assignee)
214
+ if identity_valid is not None:
215
+ return identity_valid
216
+
217
+ # Handle identity unavailable case
218
+ identity_unavailable_result = self._try_identity_unavailable_path(
219
+ assignee, identity_result
220
+ )
221
+ if identity_unavailable_result is not None:
222
+ return identity_unavailable_result
223
+
224
+ # Identity system is available but validation failed
225
+ validation_mode = identity_result.canonical_id or "local-only"
226
+
227
+ # Try fallback validation paths
228
+ fallback_result = self._try_identity_failed_path(assignee, validation_mode)
229
+ if fallback_result is not None:
230
+ return fallback_result
231
+
232
+ # No fallback available, return identity system result
233
+ if not identity_result.is_valid:
234
+ log_validation_error(
235
+ ValueError(identity_result.message),
236
+ entity_type="Assignee",
237
+ field_name="assignee",
238
+ proposed_value=assignee,
239
+ )
240
+ return False, identity_result.message, ""
241
+ except Exception as e:
242
+ log_error_with_context(
243
+ e,
244
+ operation="validate_assignee",
245
+ entity_type="Assignee",
246
+ additional_context={"assignee": assignee},
247
+ )
248
+ raise
249
+
250
+ def _should_use_github_validation(self, validation_mode: str) -> bool:
251
+ """Check if GitHub validation should be attempted.
252
+
253
+ Args:
254
+ validation_mode: Current validation mode
255
+
256
+ Returns:
257
+ True if GitHub validation should be used
258
+ """
259
+ if not self.github_config:
260
+ return False
261
+
262
+ token, owner, repo = self.github_config
263
+ if not (token and owner and repo):
264
+ return False
265
+
266
+ return validation_mode in ["hybrid", "github-only"]
267
+
268
+ def _should_use_local_validation(self, validation_mode: str) -> bool:
269
+ """Check if local validation should be attempted.
270
+
271
+ Args:
272
+ validation_mode: Current validation mode
273
+
274
+ Returns:
275
+ True if local validation should be used
276
+ """
277
+ # Use local validation if GitHub is not configured
278
+ if not self.github_config:
279
+ return validation_mode in ["local-only", "hybrid"]
280
+
281
+ token, owner, repo = self.github_config
282
+ if not (token and owner and repo):
283
+ return validation_mode in ["local-only", "hybrid"]
284
+
285
+ return False
286
+
287
+ @traced("validate_assignee_github")
288
+ def _validate_with_github(self, assignee: str) -> AssigneeValidationResult:
289
+ """Validate using GitHub.
290
+
291
+ Args:
292
+ assignee: Username to validate
293
+
294
+ Returns:
295
+ Validation result
296
+ """
297
+ if not self.github_config:
298
+ return AssigneeValidationResult(
299
+ is_valid=False, message="GitHub not configured"
300
+ )
301
+
302
+ token, owner, repo = self.github_config
303
+ # Import here to avoid hard dependency on infrastructure
304
+ from roadmap.infrastructure.validation.github_validator import (
305
+ GitHubAssigneeValidator,
306
+ )
307
+
308
+ github_validator = GitHubAssigneeValidator(
309
+ token, owner, repo, self.cached_members
310
+ )
311
+ result = github_validator.validate(assignee)
312
+ return result