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,327 @@
1
+ """Git hooks configuration command - Set up auto-sync behavior."""
2
+
3
+ from pathlib import Path
4
+
5
+ import click
6
+
7
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
8
+ from roadmap.common.console import get_console
9
+ from roadmap.core.services.git.git_hook_auto_sync_service import (
10
+ GitHookAutoSyncConfig,
11
+ GitHookAutoSyncService,
12
+ )
13
+
14
+ console = get_console()
15
+
16
+
17
+ def _any_sync_flag_set(
18
+ enable_auto_sync: bool,
19
+ disable_auto_sync: bool,
20
+ sync_on_commit: bool,
21
+ no_sync_on_commit: bool,
22
+ sync_on_checkout: bool,
23
+ no_sync_on_checkout: bool,
24
+ sync_on_merge: bool,
25
+ no_sync_on_merge: bool,
26
+ confirm: bool | None,
27
+ force_local: bool,
28
+ force_github: bool,
29
+ ) -> bool:
30
+ """Return True if any config-changing flag was explicitly provided."""
31
+ return any(
32
+ [
33
+ enable_auto_sync,
34
+ disable_auto_sync,
35
+ sync_on_commit,
36
+ no_sync_on_commit,
37
+ sync_on_checkout,
38
+ no_sync_on_checkout,
39
+ sync_on_merge,
40
+ no_sync_on_merge,
41
+ confirm is not None,
42
+ force_local,
43
+ force_github,
44
+ ]
45
+ )
46
+
47
+
48
+ def _build_new_sync_config(
49
+ current_config: "GitHookAutoSyncConfig",
50
+ enable_auto_sync: bool,
51
+ disable_auto_sync: bool,
52
+ sync_on_commit: bool,
53
+ no_sync_on_commit: bool,
54
+ sync_on_checkout: bool,
55
+ no_sync_on_checkout: bool,
56
+ sync_on_merge: bool,
57
+ no_sync_on_merge: bool,
58
+ confirm: bool | None,
59
+ force_local: bool,
60
+ force_github: bool,
61
+ ) -> "GitHookAutoSyncConfig":
62
+ """Build a new config by merging explicit flags onto the existing config."""
63
+ return GitHookAutoSyncConfig(
64
+ auto_sync_enabled=(
65
+ enable_auto_sync
66
+ or (not disable_auto_sync and current_config.auto_sync_enabled)
67
+ ),
68
+ sync_on_commit=(
69
+ sync_on_commit or (not no_sync_on_commit and current_config.sync_on_commit)
70
+ ),
71
+ sync_on_checkout=(
72
+ sync_on_checkout
73
+ or (not no_sync_on_checkout and current_config.sync_on_checkout)
74
+ ),
75
+ sync_on_merge=(
76
+ sync_on_merge or (not no_sync_on_merge and current_config.sync_on_merge)
77
+ ),
78
+ confirm_before_sync=(
79
+ confirm if confirm is not None else current_config.confirm_before_sync
80
+ ),
81
+ force_local=force_local or current_config.force_local,
82
+ force_github=force_github or current_config.force_github,
83
+ )
84
+
85
+
86
+ @click.command(name="hooks-config")
87
+ @click.option(
88
+ "--enable-auto-sync",
89
+ is_flag=True,
90
+ help="Enable auto-sync on Git events",
91
+ )
92
+ @click.option(
93
+ "--disable-auto-sync",
94
+ is_flag=True,
95
+ help="Disable all auto-sync on Git events",
96
+ )
97
+ @click.option(
98
+ "--sync-on-commit",
99
+ is_flag=True,
100
+ help="Enable auto-sync after commits",
101
+ )
102
+ @click.option(
103
+ "--no-sync-on-commit",
104
+ is_flag=True,
105
+ help="Disable auto-sync after commits",
106
+ )
107
+ @click.option(
108
+ "--sync-on-checkout",
109
+ is_flag=True,
110
+ help="Enable auto-sync on branch changes",
111
+ )
112
+ @click.option(
113
+ "--no-sync-on-checkout",
114
+ is_flag=True,
115
+ help="Disable auto-sync on branch changes",
116
+ )
117
+ @click.option(
118
+ "--sync-on-merge",
119
+ is_flag=True,
120
+ help="Enable auto-sync on merge operations",
121
+ )
122
+ @click.option(
123
+ "--no-sync-on-merge",
124
+ is_flag=True,
125
+ help="Disable auto-sync on merge operations",
126
+ )
127
+ @click.option(
128
+ "--confirm/--no-confirm",
129
+ default=None,
130
+ help="Require confirmation before syncing",
131
+ )
132
+ @click.option(
133
+ "--force-local",
134
+ is_flag=True,
135
+ help="Resolve conflicts by keeping local changes",
136
+ )
137
+ @click.option(
138
+ "--force-github",
139
+ is_flag=True,
140
+ help="Resolve conflicts by keeping GitHub changes",
141
+ )
142
+ @click.option(
143
+ "--show",
144
+ "show_config",
145
+ is_flag=True,
146
+ help="Show current configuration",
147
+ )
148
+ @click.pass_context
149
+ @require_initialized
150
+ def hooks_config(
151
+ ctx: click.Context,
152
+ enable_auto_sync: bool,
153
+ disable_auto_sync: bool,
154
+ sync_on_commit: bool,
155
+ no_sync_on_commit: bool,
156
+ sync_on_checkout: bool,
157
+ no_sync_on_checkout: bool,
158
+ sync_on_merge: bool,
159
+ no_sync_on_merge: bool,
160
+ confirm: bool | None,
161
+ force_local: bool,
162
+ force_github: bool,
163
+ show_config: bool,
164
+ ) -> None:
165
+ """Configure Git hooks auto-sync behavior.
166
+
167
+ Examples:
168
+ # Show current configuration
169
+ roadmap git hooks-config --show
170
+
171
+ # Enable auto-sync on commits with confirmation
172
+ roadmap git hooks-config --enable-auto-sync --sync-on-commit --confirm
173
+
174
+ # Configure force-local resolution for conflicts
175
+ roadmap git hooks-config --sync-on-commit --force-local
176
+
177
+ # Disable auto-sync on checkouts
178
+ roadmap git hooks-config --no-sync-on-checkout
179
+
180
+ Configuration is stored locally in the roadmap project.
181
+ """
182
+ core = ctx.obj.get("core") if isinstance(ctx.obj, dict) else ctx.obj
183
+
184
+ # Config file path
185
+ config_path = Path.cwd() / ".roadmap" / "config.json"
186
+
187
+ # Initialize service
188
+ service = GitHookAutoSyncService(core)
189
+
190
+ # Load existing config from storage if available
191
+ service.load_config_from_file(config_path)
192
+ current_config = service.get_config()
193
+
194
+ # Build new config from options
195
+ new_config = _build_new_sync_config(
196
+ current_config,
197
+ enable_auto_sync,
198
+ disable_auto_sync,
199
+ sync_on_commit,
200
+ no_sync_on_commit,
201
+ sync_on_checkout,
202
+ no_sync_on_checkout,
203
+ sync_on_merge,
204
+ no_sync_on_merge,
205
+ confirm,
206
+ force_local,
207
+ force_github,
208
+ )
209
+
210
+ # Handle conflicts in resolution options
211
+ if force_local and force_github:
212
+ console.print("[red]❌ Cannot use both --force-local and --force-github[/red]")
213
+ ctx.exit(1)
214
+
215
+ # Apply new config
216
+ service.set_config(new_config)
217
+
218
+ # Save config to file if any settings changed
219
+ any_flag = _any_sync_flag_set(
220
+ enable_auto_sync,
221
+ disable_auto_sync,
222
+ sync_on_commit,
223
+ no_sync_on_commit,
224
+ sync_on_checkout,
225
+ no_sync_on_checkout,
226
+ sync_on_merge,
227
+ no_sync_on_merge,
228
+ confirm,
229
+ force_local,
230
+ force_github,
231
+ )
232
+ if any_flag:
233
+ if service.save_config_to_file(config_path):
234
+ console.print("[dim]Config saved to .roadmap/config.json[/dim]")
235
+
236
+ # Show configuration
237
+ if show_config or not any_flag:
238
+ _display_config(new_config, service)
239
+ else:
240
+ console.print("[green]✅ Configuration updated[/green]")
241
+ _display_config(new_config, service)
242
+
243
+
244
+ def _display_config(
245
+ config: GitHookAutoSyncConfig, service: GitHookAutoSyncService
246
+ ) -> None:
247
+ """Display current auto-sync configuration."""
248
+ console.print()
249
+ console.print("[bold cyan]Git Hooks Auto-Sync Configuration[/bold cyan]")
250
+ console.print()
251
+
252
+ # Check if config is persisted
253
+ config_path = Path.cwd() / ".roadmap" / "config.json"
254
+ if config_path.exists():
255
+ console.print(f"[dim]Config file: {config_path}[/dim]")
256
+ console.print()
257
+
258
+ # Master switch
259
+ master_status = (
260
+ "[green]✓ ENABLED[/green]"
261
+ if config.auto_sync_enabled
262
+ else "[dim]✗ DISABLED[/dim]"
263
+ )
264
+ console.print(f"Master Switch: {master_status}")
265
+ console.print()
266
+
267
+ # Per-event configuration
268
+ console.print("[bold]Sync Events:[/bold]")
269
+ _print_setting(
270
+ " Commit",
271
+ config.sync_on_commit and config.auto_sync_enabled,
272
+ )
273
+ _print_setting(
274
+ " Branch Checkout",
275
+ config.sync_on_checkout and config.auto_sync_enabled,
276
+ )
277
+ _print_setting(
278
+ " Merge",
279
+ config.sync_on_merge and config.auto_sync_enabled,
280
+ )
281
+ console.print()
282
+
283
+ # Behavior settings
284
+ console.print("[bold]Behavior:[/bold]")
285
+ console.print(
286
+ f" Confirm Before Sync: {'✓ Yes' if config.confirm_before_sync else '✗ No'}"
287
+ )
288
+ console.print()
289
+
290
+ # Conflict resolution
291
+ console.print("[bold]Conflict Resolution:[/bold]")
292
+ if config.force_local:
293
+ console.print(" Strategy: [yellow]Force Local (keep local changes)[/yellow]")
294
+ elif config.force_github:
295
+ console.print(" Strategy: [yellow]Force GitHub (keep remote changes)[/yellow]")
296
+ else:
297
+ console.print(
298
+ " Strategy: [dim]None configured (manual resolution required)[/dim]"
299
+ )
300
+ console.print()
301
+
302
+ # Statistics
303
+ try:
304
+ stats = service.get_sync_stats()
305
+ console.print("[bold]Sync Statistics:[/bold]")
306
+ console.print(f" Total Issues: {stats['total_issues']}")
307
+ console.print(
308
+ f" GitHub-Linked: {stats['total_issues'] - stats['never_synced']}"
309
+ )
310
+ console.print(f" Never Synced: {stats['never_synced']}")
311
+ if stats["total_sync_attempts"] > 0:
312
+ console.print(f" Success Rate: {stats['success_rate']:.1f}%")
313
+ except Exception as e:
314
+ from roadmap.common.logging import get_logger
315
+
316
+ logger = get_logger(__name__)
317
+ logger.debug(
318
+ "sync_stats_display_failed", error=str(e), action="display_sync_stats"
319
+ )
320
+
321
+ console.print()
322
+
323
+
324
+ def _print_setting(label: str, enabled: bool) -> None:
325
+ """Print a configuration setting with status."""
326
+ status = "[green]✓ ENABLED[/green]" if enabled else "[dim]✗ disabled[/dim]"
327
+ console.print(f"{label:25} {status}")
@@ -0,0 +1,158 @@
1
+ """Display helper for git status command."""
2
+
3
+ from typing import Any
4
+
5
+ from rich.console import Console
6
+
7
+
8
+ class GitStatusDisplay:
9
+ """Handles display of git status information."""
10
+
11
+ def __init__(self, console: Console):
12
+ """Initialize with a console for output.
13
+
14
+ Args:
15
+ console: Rich console instance for formatted output
16
+ """
17
+ self.console = console
18
+
19
+ def show_not_git_repo(self) -> None:
20
+ """Display message when not in a git repository."""
21
+ self.console.print("📁 Not in a Git repository", style="yellow")
22
+
23
+ def show_header(self) -> None:
24
+ """Display the status header."""
25
+ self.console.print("🔍 Git Repository Status", style="bold blue")
26
+ self.console.print()
27
+
28
+ def show_repository_info(self, git_context: dict[str, Any]) -> None:
29
+ """Display repository origin and GitHub information.
30
+
31
+ Args:
32
+ git_context: Dictionary containing git context information
33
+ """
34
+ if git_context.get("origin_url"):
35
+ self.console.print(f"📍 Origin: {git_context['origin_url']}", style="cyan")
36
+
37
+ if git_context.get("github_owner") and git_context.get("github_repo"):
38
+ self.console.print(
39
+ f"🐙 GitHub: {git_context['github_owner']}/{git_context['github_repo']}",
40
+ style="cyan",
41
+ )
42
+
43
+ def show_current_branch(self, git_context: dict[str, Any]) -> None:
44
+ """Display current branch and linked issue information.
45
+
46
+ Args:
47
+ git_context: Dictionary containing git context information
48
+ """
49
+ if not git_context.get("current_branch"):
50
+ return
51
+
52
+ self.console.print(
53
+ f"🌿 Current branch: {git_context['current_branch']}", style="green"
54
+ )
55
+
56
+ linked_issue = git_context.get("linked_issue")
57
+ if linked_issue:
58
+ self._show_linked_issue_details(linked_issue)
59
+ else:
60
+ self.console.print(" 💡 No linked issue found", style="dim")
61
+
62
+ def _show_linked_issue_details(self, linked_issue: dict[str, Any]) -> None:
63
+ """Display details of the linked issue.
64
+
65
+ Args:
66
+ linked_issue: Dictionary containing linked issue information
67
+ """
68
+ self.console.print("🔗 Linked issue:", style="bold")
69
+ self.console.print(f" 📋 {linked_issue['title']}", style="cyan")
70
+ self.console.print(f" 🆔 {linked_issue['id']}", style="dim")
71
+ self.console.print(f" 📊 Status: {linked_issue['status']}", style="yellow")
72
+
73
+ priority_style = "red" if linked_issue["priority"] == "critical" else "yellow"
74
+ self.console.print(
75
+ f" ⚡ Priority: {linked_issue['priority']}", style=priority_style
76
+ )
77
+
78
+ def show_branch_issue_links(
79
+ self, branch_issues: dict[str, list[str]], current_branch: str, core: Any
80
+ ) -> None:
81
+ """Display all branch-issue mappings.
82
+
83
+ Args:
84
+ branch_issues: Dictionary mapping branches to issue IDs
85
+ current_branch: Name of the current branch
86
+ core: RoadmapCore instance for fetching issue details
87
+ """
88
+ if not branch_issues:
89
+ return
90
+
91
+ self.console.print("\n🌿 Branch-Issue Links:", style="bold")
92
+
93
+ for branch, issue_ids in branch_issues.items():
94
+ for issue_id in issue_ids:
95
+ issue = core.issues.get(issue_id)
96
+ if issue:
97
+ self._show_branch_issue_link(branch, issue, current_branch)
98
+
99
+ def _show_branch_issue_link(
100
+ self, branch: str, issue: Any, current_branch: str
101
+ ) -> None:
102
+ """Display a single branch-issue link.
103
+
104
+ Args:
105
+ branch: Branch name
106
+ issue: Issue object
107
+ current_branch: Name of the current branch
108
+ """
109
+ marker = "👉" if branch == current_branch else " "
110
+ title = issue.title[:50]
111
+ if len(issue.title) > 50:
112
+ title += "..."
113
+
114
+ self.console.print(f"{marker} {branch} → {title}", style="cyan")
115
+
116
+ def show_recent_commits(self, core: Any) -> None:
117
+ """Display recent commits with roadmap references.
118
+
119
+ Args:
120
+ core: RoadmapCore instance for fetching commit information
121
+ """
122
+ if not core.git.is_git_repository():
123
+ return
124
+
125
+ recent_commits = core.git.get_recent_commits(count=5)
126
+ roadmap_commits = [c for c in recent_commits if c.extract_roadmap_references()]
127
+
128
+ if not roadmap_commits:
129
+ return
130
+
131
+ self.console.print("\n📝 Recent Roadmap Commits:", style="bold")
132
+
133
+ for commit in roadmap_commits[:3]:
134
+ self._show_commit_details(commit)
135
+
136
+ def _show_commit_details(self, commit: Any) -> None:
137
+ """Display details of a single commit.
138
+
139
+ Args:
140
+ commit: Commit object with message and references
141
+ """
142
+ message = commit.message[:60]
143
+ if len(commit.message) > 60:
144
+ message += "..."
145
+
146
+ self.console.print(f" {commit.short_hash} {message}", style="dim")
147
+
148
+ refs = commit.extract_roadmap_references()
149
+ if refs:
150
+ self.console.print(f" 🔗 References: {', '.join(refs)}", style="cyan")
151
+
152
+ def show_error(self, error: Exception) -> None:
153
+ """Display an error message.
154
+
155
+ Args:
156
+ error: Exception that occurred
157
+ """
158
+ self.console.print(f"❌ Failed to get Git status: {error}", style="bold red")
@@ -0,0 +1,8 @@
1
+ """Health commands for roadmap.
2
+
3
+ Provides health scanning and diagnostics for entities.
4
+ """
5
+
6
+ from roadmap.adapters.cli.health.scan import scan
7
+
8
+ __all__ = ["scan"]
@@ -0,0 +1,204 @@
1
+ """Database integrity check command.
2
+
3
+ Checks that local issue files align with database state and baseline.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import sqlite3
9
+
10
+ import click
11
+ from structlog import get_logger
12
+
13
+ from roadmap.adapters.cli.cli_command_helpers import require_initialized
14
+ from roadmap.adapters.cli.utils.click_options import health_check_options
15
+ from roadmap.adapters.persistence.parser.issue import IssueParser
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ def _collect_local_issue_ids(issues_dir) -> tuple[set[str], list[tuple[str, str]]]:
21
+ local_ids: set[str] = set()
22
+ parse_errors: list[tuple[str, str]] = []
23
+ for file_path in issues_dir.glob("**/*.md"):
24
+ try:
25
+ issue = IssueParser.parse_issue_file(file_path)
26
+ except Exception as exc:
27
+ parse_errors.append((str(file_path), str(exc)))
28
+ continue
29
+ issue_id = getattr(issue, "id", None)
30
+ if issue_id:
31
+ local_ids.add(str(issue_id))
32
+ return local_ids, parse_errors
33
+
34
+
35
+ def _load_db_state(db_path) -> tuple[set[str], set[str], int] | None:
36
+ try:
37
+ conn = sqlite3.connect(str(db_path))
38
+ conn.row_factory = sqlite3.Row
39
+ db_ids = {row["id"] for row in conn.execute("SELECT id FROM issues").fetchall()}
40
+ baseline_ids = {
41
+ row["issue_id"]
42
+ for row in conn.execute("SELECT issue_id FROM sync_base_state").fetchall()
43
+ }
44
+ remote_links_count = conn.execute(
45
+ "SELECT COUNT(*) FROM issue_remote_links"
46
+ ).fetchone()[0]
47
+ conn.close()
48
+ return db_ids, baseline_ids, remote_links_count
49
+ except Exception as exc:
50
+ logger.warning(
51
+ "db_integrity_query_failed",
52
+ error=str(exc),
53
+ error_type=type(exc).__name__,
54
+ severity="operational",
55
+ )
56
+ return None
57
+
58
+
59
+ def _build_report(
60
+ local_ids: set[str],
61
+ db_ids: set[str],
62
+ baseline_ids: set[str],
63
+ remote_links_count: int,
64
+ parse_errors: list[tuple[str, str]],
65
+ ) -> dict:
66
+ missing_in_db = sorted(local_ids - db_ids)
67
+ extra_in_db = sorted(db_ids - local_ids)
68
+ baseline_missing_issue = sorted(baseline_ids - db_ids)
69
+ issue_missing_baseline = sorted(db_ids - baseline_ids)
70
+
71
+ return {
72
+ "local_issue_files": len(local_ids),
73
+ "db_issues": len(db_ids),
74
+ "sync_base_state": len(baseline_ids),
75
+ "remote_links": remote_links_count,
76
+ "missing_in_db": len(missing_in_db),
77
+ "extra_in_db": len(extra_in_db),
78
+ "baseline_missing_issue": len(baseline_missing_issue),
79
+ "issue_missing_baseline": len(issue_missing_baseline),
80
+ "missing_in_db_ids": missing_in_db,
81
+ "extra_in_db_ids": extra_in_db,
82
+ "baseline_missing_issue_ids": baseline_missing_issue,
83
+ "issue_missing_baseline_ids": issue_missing_baseline,
84
+ "parse_errors": parse_errors,
85
+ }
86
+
87
+
88
+ def _print_plain_report(report: dict, show_ids: bool, limit: int) -> None:
89
+ click.echo("DB Integrity Report")
90
+ click.echo(f"local_issue_files: {report['local_issue_files']}")
91
+ click.echo(f"db_issues: {report['db_issues']}")
92
+ click.echo(f"sync_base_state: {report['sync_base_state']}")
93
+ click.echo(f"remote_links: {report['remote_links']}")
94
+ click.echo(f"missing_in_db: {report['missing_in_db']}")
95
+ click.echo(f"extra_in_db: {report['extra_in_db']}")
96
+ click.echo(f"baseline_missing_issue: {report['baseline_missing_issue']}")
97
+ click.echo(f"issue_missing_baseline: {report['issue_missing_baseline']}")
98
+ click.echo(f"parse_errors: {len(report['parse_errors'])}")
99
+
100
+ if show_ids and limit > 0:
101
+ if report["missing_in_db_ids"]:
102
+ click.echo("missing_in_db_ids:")
103
+ for issue_id in report["missing_in_db_ids"][:limit]:
104
+ click.echo(f" {issue_id}")
105
+ if report["extra_in_db_ids"]:
106
+ click.echo("extra_in_db_ids:")
107
+ for issue_id in report["extra_in_db_ids"][:limit]:
108
+ click.echo(f" {issue_id}")
109
+ if report["baseline_missing_issue_ids"]:
110
+ click.echo("baseline_missing_issue_ids:")
111
+ for issue_id in report["baseline_missing_issue_ids"][:limit]:
112
+ click.echo(f" {issue_id}")
113
+ if report["issue_missing_baseline_ids"]:
114
+ click.echo("issue_missing_baseline_ids:")
115
+ for issue_id in report["issue_missing_baseline_ids"][:limit]:
116
+ click.echo(f" {issue_id}")
117
+ if report["parse_errors"]:
118
+ click.echo("parse_errors:")
119
+ for path, error in report["parse_errors"][:limit]:
120
+ click.echo(f" {path}: {error}")
121
+
122
+
123
+ @click.command(name="db-integrity")
124
+ @health_check_options
125
+ @click.option(
126
+ "--show-ids",
127
+ is_flag=True,
128
+ help="Show missing/extra ids (limited by --limit)",
129
+ )
130
+ @click.option(
131
+ "--json",
132
+ "json_output",
133
+ is_flag=True,
134
+ help="Output results as JSON",
135
+ )
136
+ @click.option(
137
+ "--limit",
138
+ type=int,
139
+ default=20,
140
+ show_default=True,
141
+ help="Max ids to display when using --show-ids",
142
+ )
143
+ @click.pass_context
144
+ @require_initialized
145
+ def db_integrity(
146
+ ctx: click.Context,
147
+ show_ids: bool,
148
+ json_output: bool,
149
+ limit: int,
150
+ verbose: bool,
151
+ details: bool,
152
+ format: str,
153
+ ) -> None:
154
+ """Check database integrity against local issue files."""
155
+ core = ctx.obj["core"]
156
+ issues_dir = core.roadmap_dir / "issues"
157
+ db_path = core.db_dir / "state.db"
158
+
159
+ if not issues_dir.exists():
160
+ click.echo("No issues directory found.")
161
+ return
162
+
163
+ if not db_path.exists():
164
+ click.echo("Database not found.")
165
+ return
166
+
167
+ local_ids, parse_errors = _collect_local_issue_ids(issues_dir)
168
+ db_state = _load_db_state(db_path)
169
+ if db_state is None:
170
+ click.echo("Failed to read database state.")
171
+ return
172
+ db_ids, baseline_ids, remote_links_count = db_state
173
+ report = _build_report(
174
+ local_ids,
175
+ db_ids,
176
+ baseline_ids,
177
+ remote_links_count,
178
+ parse_errors,
179
+ )
180
+
181
+ if json_output:
182
+ import json
183
+
184
+ if show_ids and limit > 0:
185
+ report["missing_in_db_ids"] = report["missing_in_db_ids"][:limit]
186
+ report["extra_in_db_ids"] = report["extra_in_db_ids"][:limit]
187
+ report["baseline_missing_issue_ids"] = report["baseline_missing_issue_ids"][
188
+ :limit
189
+ ]
190
+ report["issue_missing_baseline_ids"] = report["issue_missing_baseline_ids"][
191
+ :limit
192
+ ]
193
+ report["parse_errors"] = [
194
+ {"path": path, "error": error}
195
+ for path, error in report["parse_errors"][:limit]
196
+ ]
197
+ else:
198
+ report["parse_errors"] = [
199
+ {"path": path, "error": error} for path, error in report["parse_errors"]
200
+ ]
201
+ click.echo(json.dumps(report, indent=2))
202
+ return
203
+
204
+ _print_plain_report(report, show_ids, limit)