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,128 @@
1
+ """Progress tracking for sync operations."""
2
+
3
+ from contextlib import contextmanager
4
+
5
+ from rich.progress import (
6
+ BarColumn,
7
+ Progress,
8
+ SpinnerColumn,
9
+ TaskProgressColumn,
10
+ TextColumn,
11
+ TimeElapsedColumn,
12
+ )
13
+
14
+ from roadmap.common.console import get_console
15
+
16
+
17
+ class SyncProgressTracker:
18
+ """Tracks and displays progress during sync operations."""
19
+
20
+ def __init__(self, console=None):
21
+ """Initialize progress tracker.
22
+
23
+ Args:
24
+ console: Rich console instance (creates new one if None)
25
+ """
26
+ self.console = console or get_console()
27
+ self.progress = None
28
+ self.current_task = None
29
+
30
+ @contextmanager
31
+ def track_sync(self, total_issues: int):
32
+ """Context manager for tracking overall sync progress.
33
+
34
+ Args:
35
+ total_issues: Total number of issues to sync
36
+
37
+ Yields:
38
+ Progress tracker instance
39
+ """
40
+ self.progress = Progress(
41
+ SpinnerColumn(),
42
+ TextColumn("[bold blue]{task.description}"),
43
+ BarColumn(),
44
+ TaskProgressColumn(),
45
+ TextColumn("•"),
46
+ TimeElapsedColumn(),
47
+ console=self.console,
48
+ )
49
+
50
+ with self.progress:
51
+ self.current_task = self.progress.add_task(
52
+ "Syncing issues...", total=total_issues
53
+ )
54
+ yield self
55
+
56
+ def update(self, advance: int = 1, description: str | None = None):
57
+ """Update progress.
58
+
59
+ Args:
60
+ advance: Number of steps to advance
61
+ description: Optional new description
62
+ """
63
+ if self.progress and self.current_task is not None:
64
+ if description:
65
+ self.progress.update(self.current_task, description=description)
66
+ self.progress.advance(self.current_task, advance)
67
+
68
+ def set_description(self, description: str):
69
+ """Set current task description.
70
+
71
+ Args:
72
+ description: New description text
73
+ """
74
+ if self.progress and self.current_task is not None:
75
+ self.progress.update(self.current_task, description=description)
76
+
77
+ @contextmanager
78
+ def track_phase(self, phase_name: str, total: int | None = None):
79
+ """Track a specific phase of sync operation.
80
+
81
+ Args:
82
+ phase_name: Name of the phase (e.g., "Fetching", "Pushing")
83
+ total: Total items in this phase
84
+
85
+ Yields:
86
+ Task ID for this phase
87
+ """
88
+ if not self.progress:
89
+ yield None
90
+ return
91
+
92
+ task_id = self.progress.add_task(
93
+ f"[cyan]{phase_name}...",
94
+ total=total if total is not None else None,
95
+ )
96
+
97
+ try:
98
+ yield task_id
99
+ finally:
100
+ self.progress.update(task_id, completed=True)
101
+
102
+ def update_phase(self, task_id, advance: int = 1, description: str | None = None):
103
+ """Update a specific phase task.
104
+
105
+ Args:
106
+ task_id: Task ID returned from track_phase
107
+ advance: Number of steps to advance
108
+ description: Optional new description
109
+ """
110
+ if self.progress and task_id is not None:
111
+ if description:
112
+ self.progress.update(task_id, description=description)
113
+ if advance:
114
+ self.progress.advance(task_id, advance)
115
+
116
+
117
+ def create_spinner_progress() -> Progress:
118
+ """Create a simple spinner progress for indeterminate operations.
119
+
120
+ Returns:
121
+ Progress instance with spinner
122
+ """
123
+ return Progress(
124
+ SpinnerColumn(),
125
+ TextColumn("[bold blue]{task.description}"),
126
+ TimeElapsedColumn(),
127
+ console=get_console(),
128
+ )
@@ -0,0 +1,190 @@
1
+ """CLI command for displaying and querying sync metrics.
2
+
3
+ This module provides commands to view sync operation metrics,
4
+ historical data, and aggregate statistics.
5
+ """
6
+
7
+ import click
8
+ from rich.panel import Panel
9
+
10
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
11
+ from roadmap.adapters.persistence.sync_metrics_repository import (
12
+ SyncMetricsRepository,
13
+ )
14
+ from roadmap.common.console import get_console
15
+ from roadmap.presentation.formatters.sync_metrics_formatter import (
16
+ create_metrics_history_table,
17
+ create_metrics_summary_table,
18
+ )
19
+
20
+
21
+ @click.command(name="metrics")
22
+ @click.option(
23
+ "--backend",
24
+ type=str,
25
+ default=None,
26
+ help="Filter metrics by backend type (e.g., github, vanilla_git)",
27
+ )
28
+ @click.option(
29
+ "--days",
30
+ type=int,
31
+ default=7,
32
+ help="Show metrics from the last N days (default: 7)",
33
+ )
34
+ @click.option(
35
+ "--latest",
36
+ is_flag=True,
37
+ help="Show only the latest sync metrics",
38
+ )
39
+ @click.option(
40
+ "--stats",
41
+ is_flag=True,
42
+ help="Show aggregate statistics for the period",
43
+ )
44
+ @click.option(
45
+ "--verbose",
46
+ "-v",
47
+ is_flag=True,
48
+ help="Show detailed metrics including performance data",
49
+ )
50
+ @click.pass_context
51
+ @require_initialized
52
+ def sync_metrics(
53
+ ctx: click.Context,
54
+ backend: str | None,
55
+ days: int,
56
+ latest: bool,
57
+ stats: bool,
58
+ verbose: bool,
59
+ ) -> None:
60
+ r"""Display sync operation metrics and history.
61
+
62
+ View historical sync metrics, performance data, and statistics
63
+ to monitor sync health and performance over time.
64
+
65
+ Examples:
66
+ \b
67
+ # Show metrics from last 7 days
68
+ roadmap sync metrics
69
+
70
+ \b
71
+ # Show metrics from last 30 days for GitHub
72
+ roadmap sync metrics --backend github --days 30
73
+
74
+ \b
75
+ # Show only the latest sync
76
+ roadmap sync metrics --latest
77
+
78
+ \b
79
+ # Show aggregate statistics
80
+ roadmap sync metrics --stats
81
+ """
82
+ console_inst = get_console()
83
+ core = ctx.obj["core"]
84
+
85
+ try:
86
+ # Initialize repository with core's database manager
87
+ metrics_repo = SyncMetricsRepository(core.db_manager)
88
+
89
+ # Get metrics
90
+ if latest:
91
+ # Show only the latest sync
92
+ metrics = metrics_repo.get_latest(backend_type=backend)
93
+ if not metrics:
94
+ console_inst.print("[yellow]No sync metrics found[/yellow]")
95
+ return
96
+
97
+ console_inst.print()
98
+ console_inst.print(
99
+ Panel(
100
+ create_metrics_summary_table(metrics, verbose),
101
+ title="[bold cyan]Latest Sync Metrics[/bold cyan]",
102
+ border_style="cyan",
103
+ )
104
+ )
105
+ console_inst.print()
106
+
107
+ elif stats:
108
+ # Show aggregate statistics
109
+ statistics = metrics_repo.get_statistics(backend_type=backend, days=days)
110
+
111
+ if statistics and statistics["total_syncs"] > 0:
112
+ from rich.table import Table
113
+
114
+ stats_table = Table(show_header=False, box=None, padding=(0, 2))
115
+ stats_table.add_column(style="bold")
116
+ stats_table.add_column(justify="right", style="cyan")
117
+
118
+ stats_table.add_row(
119
+ "Total Syncs",
120
+ str(statistics["total_syncs"]),
121
+ )
122
+ stats_table.add_row(
123
+ "Average Duration",
124
+ f"{statistics['avg_duration_seconds']:.1f}s",
125
+ )
126
+ stats_table.add_row(
127
+ "Total Duplicates",
128
+ str(statistics["total_duplicates_detected"]),
129
+ )
130
+ stats_table.add_row(
131
+ "Total Conflicts",
132
+ str(statistics["total_conflicts_detected"]),
133
+ )
134
+ stats_table.add_row(
135
+ "Total Errors",
136
+ str(statistics["total_errors"]),
137
+ )
138
+
139
+ console_inst.print()
140
+ console_inst.print(
141
+ Panel(
142
+ stats_table,
143
+ title=f"[bold cyan]Statistics ({days} days)[/bold cyan]",
144
+ border_style="cyan",
145
+ )
146
+ )
147
+ console_inst.print()
148
+ else:
149
+ console_inst.print(
150
+ "[yellow]No sync metrics found for the specified period[/yellow]"
151
+ )
152
+
153
+ else:
154
+ # Show history
155
+ metrics_list = metrics_repo.list_by_date(backend_type=backend, days=days)
156
+
157
+ if not metrics_list:
158
+ console_inst.print(
159
+ f"[yellow]No sync metrics found in the last {days} days[/yellow]"
160
+ )
161
+ return
162
+
163
+ console_inst.print()
164
+ console_inst.print(
165
+ Panel(
166
+ create_metrics_history_table(metrics_list, limit=10),
167
+ title=f"[bold cyan]Sync History (last {days} days)[/bold cyan]",
168
+ border_style="cyan",
169
+ )
170
+ )
171
+ console_inst.print()
172
+
173
+ # Show brief summary
174
+ latest_metrics = metrics_list[0] if metrics_list else None
175
+ if latest_metrics and verbose:
176
+ console_inst.print(
177
+ Panel(
178
+ create_metrics_summary_table(latest_metrics, verbose),
179
+ title="[bold cyan]Latest Sync Details[/bold cyan]",
180
+ border_style="cyan",
181
+ )
182
+ )
183
+ console_inst.print()
184
+
185
+ except Exception as e:
186
+ console_inst.print(
187
+ f"[bold red]❌ Error retrieving metrics: {str(e)}[/bold red]"
188
+ )
189
+ if verbose:
190
+ raise
@@ -0,0 +1,58 @@
1
+ """Presentation helpers for the `roadmap sync` CLI command.
2
+
3
+ This module contains functions responsible for printing analysis results
4
+ and prompting the user for confirmation. It centralizes UI logic so that
5
+ the sync command can remain focused on orchestration.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from roadmap.common.console import get_console
11
+
12
+
13
+ def present_analysis(analysis_report: Any, verbose: bool = False) -> None:
14
+ """Present a summary (and optional details) of the sync analysis.
15
+
16
+ The function is defensive about which attributes exist on
17
+ `analysis_report` to avoid tight coupling with concrete report classes.
18
+ """
19
+ console = get_console()
20
+
21
+ console.print("\n[bold cyan]📈 Sync Analysis[/bold cyan]")
22
+ console.print(
23
+ f" ✓ Up-to-date: {getattr(analysis_report, 'issues_up_to_date', 0)}"
24
+ )
25
+ console.print(
26
+ f" 📤 Needs Push: {getattr(analysis_report, 'issues_needs_push', 0)}"
27
+ )
28
+ console.print(
29
+ f" 📥 Needs Pull: {getattr(analysis_report, 'issues_needs_pull', 0)}"
30
+ )
31
+ console.print(
32
+ f" ✓ Potential Conflicts: {getattr(analysis_report, 'conflicts_detected', 0)}"
33
+ )
34
+
35
+ if verbose:
36
+ # If the report exposes a list of changes, show a concise table-like view
37
+ changes = getattr(analysis_report, "changes", None)
38
+ if changes:
39
+ console.print("\n[bold]Detailed changes:[/bold]")
40
+ for change in changes:
41
+ try:
42
+ line = f" • {change.issue_id}: "
43
+ if getattr(change, "has_conflict", False):
44
+ line += "(CONFLICT) "
45
+ title = getattr(change, "title", None)
46
+ if title:
47
+ line += f"{title}"
48
+ console.print(line)
49
+ except Exception as e:
50
+ from roadmap.common.logging import get_logger
51
+
52
+ logger = get_logger(__name__)
53
+ logger.debug(
54
+ "sync_change_presentation_failed",
55
+ error=str(e),
56
+ action="present_change",
57
+ )
58
+ continue
@@ -0,0 +1,295 @@
1
+ """Sync validation commands for checking remote link health.
2
+
3
+ This module provides commands to validate and repair remote links in the database,
4
+ ensuring that all YAML remote_ids are properly tracked and accessible.
5
+ """
6
+
7
+ import sys
8
+
9
+ import click
10
+
11
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
12
+ from roadmap.common.console import get_console
13
+ from roadmap.common.logging import get_logger
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ @click.command(name="validate-links")
19
+ @click.option(
20
+ "--auto-fix",
21
+ is_flag=True,
22
+ help="Automatically repair broken or missing links",
23
+ )
24
+ @click.option(
25
+ "--prune-extra",
26
+ is_flag=True,
27
+ help="Remove database links that are not present in YAML",
28
+ )
29
+ @click.option(
30
+ "--dedupe",
31
+ is_flag=True,
32
+ help="Remove duplicate remote_id links (keep YAML-backed link if present)",
33
+ )
34
+ @click.option(
35
+ "--dry-run",
36
+ is_flag=True,
37
+ help="Preview changes without applying them",
38
+ )
39
+ @click.option(
40
+ "--verbose",
41
+ is_flag=True,
42
+ help="Show detailed validation information for each issue",
43
+ )
44
+ @click.pass_context
45
+ @require_initialized
46
+ def validate_links(
47
+ ctx: click.Context,
48
+ auto_fix: bool,
49
+ prune_extra: bool,
50
+ dedupe: bool,
51
+ dry_run: bool,
52
+ verbose: bool,
53
+ ) -> None:
54
+ """Validate remote links in the database against YAML files.
55
+
56
+ This command scans all YAML files to verify that remote_ids are:
57
+ 1. Properly loaded in the database
58
+ 2. Match between YAML source and database cache
59
+ 3. Valid and accessible for sync operations
60
+
61
+ **Examples:**
62
+ # Check link health
63
+ roadmap sync validate-links
64
+
65
+ # Show detailed info about each issue
66
+ roadmap sync validate-links --verbose
67
+
68
+ # Preview what would be fixed
69
+ roadmap sync validate-links --auto-fix --dry-run
70
+
71
+ # Repair broken links
72
+ roadmap sync validate-links --auto-fix
73
+ """
74
+ core = ctx.obj["core"]
75
+ console = get_console()
76
+
77
+ try:
78
+ issues_dir = core.issues_dir
79
+ if not issues_dir.exists():
80
+ console.print(
81
+ f"❌ Issues directory not found: {issues_dir}", style="bold red"
82
+ )
83
+ sys.exit(1)
84
+
85
+ validation_data = core.validation.collect_remote_link_validation_data(
86
+ issues_dir
87
+ )
88
+ issue_files = validation_data["issue_files"]
89
+ if not issue_files:
90
+ console.print("ℹ️ No issue files found", style="yellow")
91
+ return
92
+
93
+ validation_report: dict = {
94
+ "total_files": len(issue_files),
95
+ "files_with_remote_ids": 0,
96
+ "database_links": 0,
97
+ "discrepancies": [],
98
+ "missing_in_db": [],
99
+ "extra_in_db": [],
100
+ "duplicate_remote_ids": {},
101
+ }
102
+ yaml_remote_ids = validation_data["yaml_remote_ids"]
103
+ unparseable_files = validation_data["unparseable_files"]
104
+ db_links = validation_data["db_links"]
105
+ validation_report.update(
106
+ core.validation.build_remote_link_report(yaml_remote_ids, db_links)
107
+ )
108
+
109
+ _display_validation_summary(validation_report, unparseable_files, console)
110
+
111
+ if auto_fix:
112
+ _apply_auto_fix(
113
+ core,
114
+ yaml_remote_ids,
115
+ validation_report,
116
+ console,
117
+ dry_run,
118
+ verbose,
119
+ prune_extra,
120
+ dedupe,
121
+ )
122
+
123
+ if validation_report["missing_in_db"] and (not auto_fix or dry_run):
124
+ sys.exit(1)
125
+
126
+ console.print(
127
+ "\n✅ All remote links are valid and synchronized", style="bold green"
128
+ )
129
+
130
+ except Exception as e:
131
+ logger.error(
132
+ "validation_failed",
133
+ error=str(e),
134
+ error_type=type(e).__name__,
135
+ severity="system_error",
136
+ )
137
+ console.print(f"❌ Validation failed: {str(e)}", style="bold red")
138
+ sys.exit(1)
139
+
140
+
141
+ def _display_validation_summary(
142
+ validation_report: dict, unparseable_files: list, console
143
+ ) -> None:
144
+ """Print the remote-link validation summary to the console."""
145
+ console.print("\n[bold]Remote Links Validation Report[/bold]", style="bold cyan")
146
+ console.print(f"Total issue files: {validation_report['total_files']}")
147
+ console.print(
148
+ f"Files with remote_ids: {validation_report['files_with_remote_ids']}"
149
+ )
150
+ console.print(f"Database links: {validation_report['database_links']}")
151
+
152
+ missing = validation_report["missing_in_db"]
153
+ if missing:
154
+ console.print(f"\n[bold red]❌ Missing in database: {len(missing)}[/bold red]")
155
+ for issue_uuid in missing[:5]:
156
+ console.print(f" - {issue_uuid}")
157
+ if len(missing) > 5:
158
+ console.print(f" ... and {len(missing) - 5} more")
159
+
160
+ if validation_report["extra_in_db"]:
161
+ console.print(
162
+ f"\n[bold yellow]⚠️ In database but not in YAML: {len(validation_report['extra_in_db'])}[/bold yellow]"
163
+ )
164
+ console.print(" (These may be archived issues)")
165
+
166
+ dup_ids = validation_report["duplicate_remote_ids"]
167
+ if dup_ids:
168
+ console.print(
169
+ f"\n[bold yellow]⚠️ Duplicate remote IDs: {len(dup_ids)}[/bold yellow]"
170
+ )
171
+ for remote_id, issue_uuids in list(dup_ids.items())[:5]:
172
+ console.print(f" - {remote_id}: {', '.join(issue_uuids)}", style="dim")
173
+ if len(dup_ids) > 5:
174
+ console.print(f" ... and {len(dup_ids) - 5} more")
175
+
176
+ if unparseable_files:
177
+ console.print(
178
+ f"\n[bold yellow]⚠️ Could not parse {len(unparseable_files)} files[/bold yellow]"
179
+ )
180
+ for file_path, error in unparseable_files[:3]:
181
+ console.print(f" - {file_path}: {error}")
182
+ if len(unparseable_files) > 3:
183
+ console.print(f" ... and {len(unparseable_files) - 3} more")
184
+
185
+
186
+ def _apply_auto_fix(
187
+ core,
188
+ yaml_remote_ids: dict,
189
+ validation_report: dict,
190
+ console,
191
+ dry_run: bool,
192
+ verbose: bool,
193
+ prune_extra: bool,
194
+ dedupe: bool,
195
+ ) -> None:
196
+ """Apply automatic fixes for missing remote links.
197
+
198
+ Args:
199
+ core: RoadmapCore instance with validation coordinator
200
+ yaml_remote_ids: Dict of issue_uuid -> {backend -> remote_id}
201
+ validation_report: Validation report dict to update
202
+ console: Console instance for output
203
+ dry_run: If True, don't actually apply changes
204
+ verbose: If True, show detailed information
205
+ prune_extra: If True, remove links missing from YAML
206
+ dedupe: If True, remove duplicate remote IDs
207
+ """
208
+ missing = validation_report["missing_in_db"]
209
+ if not _has_auto_fix_work(missing, prune_extra, dedupe):
210
+ console.print("\n✅ No links need fixing", style="bold green")
211
+ return
212
+
213
+ _display_auto_fix_plan(
214
+ console,
215
+ missing,
216
+ yaml_remote_ids,
217
+ validation_report,
218
+ dry_run,
219
+ verbose,
220
+ prune_extra,
221
+ dedupe,
222
+ )
223
+
224
+ try:
225
+ results = core.validation.apply_remote_link_fixes(
226
+ yaml_remote_ids,
227
+ validation_report,
228
+ backend_name="github",
229
+ prune_extra=prune_extra,
230
+ dedupe=dedupe,
231
+ dry_run=dry_run,
232
+ )
233
+ except Exception as e:
234
+ logger.warning("auto_fix_failed", error=str(e), severity="operational")
235
+ console.print(f" ⚠️ Auto-fix failed: {str(e)}", style="yellow")
236
+ return
237
+
238
+ _display_auto_fix_results(console, results, dry_run)
239
+
240
+
241
+ def _has_auto_fix_work(missing: list[str], prune_extra: bool, dedupe: bool) -> bool:
242
+ return bool(missing or prune_extra or dedupe)
243
+
244
+
245
+ def _display_auto_fix_plan(
246
+ console,
247
+ missing: list[str],
248
+ yaml_remote_ids: dict,
249
+ validation_report: dict,
250
+ dry_run: bool,
251
+ verbose: bool,
252
+ prune_extra: bool,
253
+ dedupe: bool,
254
+ ) -> None:
255
+ if missing:
256
+ console.print(
257
+ f"\n[bold cyan]🔧 Auto-fixing {len(missing)} missing links...[/bold cyan]"
258
+ )
259
+ if dry_run:
260
+ console.print(
261
+ " (Dry-run mode - no changes will be made)", style="dim yellow"
262
+ )
263
+
264
+ if verbose and yaml_remote_ids:
265
+ for issue_uuid, remote_ids in yaml_remote_ids.items():
266
+ console.print(
267
+ f" 📄 {issue_uuid}: {', '.join(remote_ids.keys())}", style="cyan"
268
+ )
269
+
270
+ if prune_extra and validation_report.get("extra_in_db"):
271
+ console.print(
272
+ f"\n[bold cyan]🧹 Removing {len(validation_report['extra_in_db'])} extra links...[/bold cyan]"
273
+ )
274
+
275
+ if dedupe and validation_report.get("duplicate_remote_ids"):
276
+ console.print(
277
+ f"\n[bold cyan]🧹 Removing {len(validation_report['duplicate_remote_ids'])} duplicate remote IDs...[/bold cyan]"
278
+ )
279
+
280
+
281
+ def _display_auto_fix_results(console, results: dict, dry_run: bool) -> None:
282
+ if results.get("fixed_count"):
283
+ console.print(
284
+ f"\n[bold green]✅ Fixed {results['fixed_count']} missing remote links[/bold green]"
285
+ )
286
+ if results.get("removed_count"):
287
+ console.print(
288
+ f"[bold green]✅ Removed {results['removed_count']} extra remote links[/bold green]"
289
+ )
290
+ if results.get("deduped_count"):
291
+ console.print(
292
+ f"[bold green]✅ Removed {results['deduped_count']} duplicate remote links[/bold green]"
293
+ )
294
+ if dry_run:
295
+ console.print("Re-run without --dry-run to apply changes", style="dim yellow")