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,252 @@
1
+ """Fixer for orphaned issues (issues not in correct folder based on milestone)."""
2
+
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ from structlog import get_logger
7
+
8
+ from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
9
+ from roadmap.core.domain.issue import Status
10
+
11
+ logger = get_logger()
12
+
13
+
14
+ class OrphanedIssuesFixer(HealthFixer):
15
+ """Moves issues to correct folders based on their milestone assignment.
16
+
17
+ Safety: SAFE (moves files based on explicit metadata)
18
+
19
+ "Orphaned" = issues in wrong folder (not matching their milestone).
20
+ Fixes by moving issues to correct folder:
21
+ - .roadmap/issues/{milestone}/ for open issues
22
+ - .roadmap/archive/issues/{milestone}/ for closed issues
23
+ """
24
+
25
+ @property
26
+ def fix_type(self) -> str:
27
+ """Return fixer type identifier."""
28
+ return "orphaned_issues"
29
+
30
+ @property
31
+ def safety_level(self) -> FixSafety:
32
+ """Return safety level - SAFE because we use explicit metadata."""
33
+ return FixSafety.SAFE
34
+
35
+ @property
36
+ def description(self) -> str:
37
+ """Return fixer description."""
38
+ return "Move unassigned/misplaced issues to backlog or correct folders based on milestone assignment"
39
+
40
+ def scan(self) -> dict:
41
+ """Scan for orphaned issues (in wrong folder).
42
+
43
+ Returns:
44
+ Dict with found, count, message, details
45
+ """
46
+ misplaced = self._find_misplaced_issues()
47
+
48
+ return {
49
+ "found": len(misplaced) > 0,
50
+ "count": len(misplaced),
51
+ "message": f"Found {len(misplaced)} issue(s) in wrong folder",
52
+ "details": [{"id": iss["id"], "title": iss["title"]} for iss in misplaced],
53
+ }
54
+
55
+ def dry_run(self) -> FixResult:
56
+ """Preview which issues would be moved.
57
+
58
+ Returns:
59
+ FixResult with dry_run=True
60
+ """
61
+ misplaced = self._find_misplaced_issues()
62
+
63
+ return FixResult(
64
+ fix_type=self.fix_type,
65
+ success=True,
66
+ dry_run=True,
67
+ message=f"Would move {len(misplaced)} issue(s) to correct folder(s)",
68
+ affected_items=[
69
+ f"{iss['id']} → {iss['target_folder']}" for iss in misplaced
70
+ ],
71
+ items_count=len(misplaced),
72
+ changes_made=0,
73
+ )
74
+
75
+ def apply(self, force: bool = False) -> FixResult:
76
+ """Move orphaned issues to correct folders.
77
+
78
+ Args:
79
+ force: Ignored (SAFE fixers apply automatically)
80
+
81
+ Returns:
82
+ FixResult with move results
83
+ """
84
+ _ = force
85
+ misplaced = self._find_misplaced_issues()
86
+ moved_count = 0
87
+ failed_items = []
88
+
89
+ # Ensure backlog folder exists
90
+ backlog_path = Path(".roadmap/issues/backlog").resolve()
91
+ backlog_path.mkdir(parents=True, exist_ok=True)
92
+
93
+ issues_dir = Path(".roadmap/issues").resolve()
94
+
95
+ for issue_data in misplaced:
96
+ try:
97
+ issue_id = issue_data["id"]
98
+ target_folder = Path(issue_data["target_folder"]).resolve()
99
+ current_folder = Path(issue_data["current_folder"]).resolve()
100
+
101
+ # If milestone folder doesn't exist and not already at backlog, move to backlog
102
+ if not target_folder.exists() and current_folder != backlog_path:
103
+ target_folder = backlog_path
104
+
105
+ # Ensure target folder exists
106
+ target_folder.mkdir(parents=True, exist_ok=True)
107
+
108
+ # Find the actual issue file
109
+ issue_file = None
110
+ if current_folder == issues_dir:
111
+ # File is at root level
112
+ for f in issues_dir.glob(f"{issue_id}*.md"):
113
+ if f.is_file():
114
+ issue_file = f
115
+ break
116
+ else:
117
+ # File is in a subfolder
118
+ for f in current_folder.glob(f"{issue_id}*.md"):
119
+ if f.is_file():
120
+ issue_file = f
121
+ break
122
+
123
+ if issue_file:
124
+ # Move file to target folder
125
+ target_file = target_folder / issue_file.name
126
+ shutil.move(str(issue_file), str(target_file))
127
+ moved_count += 1
128
+ else:
129
+ failed_items.append(issue_id)
130
+ except Exception as e:
131
+ logger.error(
132
+ "move_orphaned_issue_failed",
133
+ issue_id=issue_data["id"],
134
+ error=str(e),
135
+ severity="system_error",
136
+ )
137
+ failed_items.append(issue_data["id"])
138
+
139
+ return FixResult(
140
+ fix_type=self.fix_type,
141
+ success=len(failed_items) == 0,
142
+ dry_run=False,
143
+ message=f"Moved {moved_count}/{len(misplaced)} issue(s) to correct folder(s)",
144
+ affected_items=[
145
+ iss["id"] for iss in misplaced if iss["id"] not in failed_items
146
+ ],
147
+ items_count=len(misplaced),
148
+ changes_made=moved_count,
149
+ )
150
+
151
+ def _find_misplaced_issues(self) -> list[dict]:
152
+ """Find issues that are in the wrong folder.
153
+
154
+ Returns:
155
+ List of dicts with id, title, current_folder, target_folder, milestone
156
+ """
157
+ misplaced = []
158
+
159
+ try:
160
+ issues_dir = Path(".roadmap/issues").resolve()
161
+ archive_dir = Path(".roadmap/archive/issues").resolve()
162
+
163
+ if not issues_dir.exists():
164
+ return misplaced
165
+
166
+ # Scan all issues
167
+ for issue in self.core.issues.list():
168
+ # Determine expected folder
169
+ expected_folder = self._get_expected_folder(issue)
170
+
171
+ # Find actual file and its folder
172
+ actual_folder = self._find_issue_folder(
173
+ issue.id, issues_dir, archive_dir
174
+ )
175
+
176
+ # Issue is misplaced if:
177
+ # 1. It's at root level (actual_folder == issues_dir)
178
+ # 2. Or it's in wrong folder (actual_folder != expected_folder)
179
+ if actual_folder:
180
+ if actual_folder == issues_dir or actual_folder != expected_folder:
181
+ misplaced.append(
182
+ {
183
+ "id": issue.id,
184
+ "title": issue.title,
185
+ "milestone": issue.milestone or "(none)",
186
+ "current_folder": str(actual_folder),
187
+ "target_folder": str(expected_folder),
188
+ }
189
+ )
190
+
191
+ except Exception as e:
192
+ logger.error(
193
+ "find_orphaned_issues_failed", error=str(e), severity="data_error"
194
+ )
195
+ return []
196
+
197
+ return misplaced
198
+
199
+ def _get_expected_folder(self, issue) -> Path:
200
+ """Determine the expected folder for an issue.
201
+
202
+ Args:
203
+ issue: Issue object with milestone and status
204
+
205
+ Returns:
206
+ Path to the expected folder
207
+ """
208
+ # Closed issues go to archive
209
+ base_path = (
210
+ Path(".roadmap/archive/issues").resolve()
211
+ if issue.status == Status.CLOSED
212
+ else Path(".roadmap/issues").resolve()
213
+ )
214
+
215
+ # If no milestone, assign to backlog
216
+ milestone = issue.milestone or "backlog"
217
+
218
+ return base_path / milestone
219
+
220
+ def _find_issue_folder(
221
+ self, issue_id: str, issues_dir: Path, archive_dir: Path
222
+ ) -> Path | None:
223
+ """Find the actual folder containing an issue.
224
+
225
+ Args:
226
+ issue_id: Issue ID to find
227
+ issues_dir: Path to .roadmap/issues
228
+ archive_dir: Path to .roadmap/archive/issues
229
+
230
+ Returns:
231
+ Path to the folder containing the issue, or None
232
+ """
233
+ # Check if file is at root level (loose file)
234
+ for file in issues_dir.glob(f"{issue_id}*.md"):
235
+ if file.is_file():
236
+ return issues_dir # Return root to indicate it's loose
237
+
238
+ # Check main issues directory subfolders
239
+ for folder in issues_dir.iterdir():
240
+ if folder.is_dir():
241
+ for _file in folder.glob(f"{issue_id}*.md"):
242
+ return folder
243
+
244
+ # Check archive directory
245
+ archive_dir = Path(".roadmap/archive/issues").resolve()
246
+ if archive_dir.exists():
247
+ for folder in archive_dir.iterdir():
248
+ if folder.is_dir():
249
+ for _file in folder.glob(f"{issue_id}*.md"):
250
+ return folder
251
+
252
+ return None
@@ -0,0 +1,250 @@
1
+ """Health check result formatters for various output formats.
2
+
3
+ Supports:
4
+ - Plain text: Human-readable with status icons
5
+ - Plain text with details: Includes recommendations and fix commands
6
+ - JSON: Hierarchical structure with all details
7
+ - JSON simple: Flat structure for tool integration
8
+ """
9
+
10
+ import json
11
+ from collections.abc import Mapping
12
+
13
+ import click
14
+
15
+ from roadmap.adapters.cli.health.enhancer import HealthCheckDetail, HealthCheckEnhancer
16
+ from roadmap.core.domain.health import HealthStatus
17
+ from roadmap.infrastructure.observability.health import HealthCheck
18
+
19
+
20
+ class HealthCheckFormatter:
21
+ """Formats health check results in various output styles."""
22
+
23
+ # Status display mappings
24
+ STATUS_ICONS = {
25
+ HealthStatus.HEALTHY: "✅",
26
+ HealthStatus.DEGRADED: "⚠️",
27
+ HealthStatus.UNHEALTHY: "❌",
28
+ }
29
+
30
+ STATUS_COLORS = {
31
+ HealthStatus.HEALTHY: "green",
32
+ HealthStatus.DEGRADED: "yellow",
33
+ HealthStatus.UNHEALTHY: "red",
34
+ }
35
+
36
+ def __init__(self, core=None):
37
+ """Initialize formatter.
38
+
39
+ Args:
40
+ core: Optional core instance for entity lookups
41
+ """
42
+ self.core = core
43
+ self.enhancer = HealthCheckEnhancer(core)
44
+
45
+ def format_plain(
46
+ self,
47
+ checks: Mapping[str, tuple[HealthStatus, str]],
48
+ details: bool = False,
49
+ ) -> str:
50
+ """Format checks as plain text.
51
+
52
+ Args:
53
+ checks: Dict of check results from run_all_checks()
54
+ details: If True, include recommendations and fix commands
55
+
56
+ Returns:
57
+ Formatted plain text string
58
+ """
59
+ lines = []
60
+
61
+ # Format each check
62
+ for check_name, (status, message) in checks.items():
63
+ display_name = check_name.replace("_", " ").title()
64
+ icon = self.STATUS_ICONS.get(status, "?")
65
+
66
+ lines.append(f"{icon} {display_name}: {status.value.upper()}")
67
+ if message:
68
+ lines.append(f" {message}")
69
+
70
+ # Add details if requested
71
+ if details:
72
+ enhanced = self.enhancer.enhance_check_result(
73
+ check_name, status, message
74
+ )
75
+ self._append_details(lines, enhanced)
76
+
77
+ lines.append("")
78
+
79
+ # Add overall status
80
+ overall_status = HealthCheck.get_overall_status(checks)
81
+ overall_icon = self.STATUS_ICONS.get(overall_status, "?")
82
+ overall_color = self.STATUS_COLORS.get(overall_status, "white")
83
+
84
+ lines.append("─" * 40)
85
+ click.secho(
86
+ f"{overall_icon} Overall Status: {overall_status.value.upper()}",
87
+ fg=overall_color,
88
+ bold=True,
89
+ )
90
+
91
+ return "\n".join(lines)
92
+
93
+ def format_json(
94
+ self,
95
+ checks: Mapping[str, tuple[HealthStatus, str]],
96
+ details: bool = False,
97
+ hierarchical: bool = True,
98
+ ) -> str:
99
+ """Format checks as JSON.
100
+
101
+ Args:
102
+ checks: Dict of check results from run_all_checks()
103
+ details: If True, include detailed information
104
+ hierarchical: If True, use nested structure; else flat
105
+
106
+ Returns:
107
+ JSON string
108
+ """
109
+ if hierarchical and details:
110
+ return self._format_json_hierarchical(checks)
111
+ elif hierarchical:
112
+ return self._format_json_hierarchical_simple(checks)
113
+ else:
114
+ return self._format_json_flat(checks, details)
115
+
116
+ def _format_json_hierarchical(
117
+ self, checks: Mapping[str, tuple[HealthStatus, str]]
118
+ ) -> str:
119
+ """Format as hierarchical JSON with full details."""
120
+ enhanced_checks = self.enhancer.enhance_all_checks(checks)
121
+
122
+ # Group by status
123
+ by_status = {
124
+ HealthStatus.HEALTHY: [],
125
+ HealthStatus.DEGRADED: [],
126
+ HealthStatus.UNHEALTHY: [],
127
+ }
128
+
129
+ for _, enhanced in enhanced_checks.items():
130
+ by_status[enhanced.status].append(enhanced)
131
+
132
+ # Build hierarchical structure
133
+ output = {
134
+ "metadata": {
135
+ "total_checks": len(checks),
136
+ "healthy": len(by_status[HealthStatus.HEALTHY]),
137
+ "degraded": len(by_status[HealthStatus.DEGRADED]),
138
+ "unhealthy": len(by_status[HealthStatus.UNHEALTHY]),
139
+ "overall_status": HealthCheck.get_overall_status(checks).value.upper(),
140
+ },
141
+ "checks": {
142
+ "healthy": [c.to_dict() for c in by_status[HealthStatus.HEALTHY]],
143
+ "degraded": [c.to_dict() for c in by_status[HealthStatus.DEGRADED]],
144
+ "unhealthy": [c.to_dict() for c in by_status[HealthStatus.UNHEALTHY]],
145
+ },
146
+ "next_steps": self._get_next_steps(enhanced_checks),
147
+ }
148
+
149
+ return json.dumps(output, indent=2, default=str)
150
+
151
+ def _format_json_hierarchical_simple(
152
+ self, checks: Mapping[str, tuple[HealthStatus, str]]
153
+ ) -> str:
154
+ """Format as hierarchical JSON without details."""
155
+ # Group by status
156
+ by_status = {
157
+ HealthStatus.HEALTHY: [],
158
+ HealthStatus.DEGRADED: [],
159
+ HealthStatus.UNHEALTHY: [],
160
+ }
161
+
162
+ for check_name, (status, message) in checks.items():
163
+ by_status[status].append(
164
+ {
165
+ "check": check_name,
166
+ "status": status.value.upper(),
167
+ "message": message,
168
+ }
169
+ )
170
+
171
+ output = {
172
+ "metadata": {
173
+ "total_checks": len(checks),
174
+ "healthy": len(by_status[HealthStatus.HEALTHY]),
175
+ "degraded": len(by_status[HealthStatus.DEGRADED]),
176
+ "unhealthy": len(by_status[HealthStatus.UNHEALTHY]),
177
+ "overall_status": HealthCheck.get_overall_status(checks).value.upper(),
178
+ },
179
+ "checks": {
180
+ "healthy": by_status[HealthStatus.HEALTHY],
181
+ "degraded": by_status[HealthStatus.DEGRADED],
182
+ "unhealthy": by_status[HealthStatus.UNHEALTHY],
183
+ },
184
+ }
185
+
186
+ return json.dumps(output, indent=2, default=str)
187
+
188
+ def _format_json_flat(
189
+ self, checks: Mapping[str, tuple[HealthStatus, str]], details: bool = False
190
+ ) -> str:
191
+ """Format as flat JSON."""
192
+ output = {
193
+ "checks": {},
194
+ "overall_status": HealthCheck.get_overall_status(checks).value.upper(),
195
+ }
196
+
197
+ for check_name, (status, message) in checks.items():
198
+ check_data = {
199
+ "status": status.value.upper(),
200
+ "message": message,
201
+ }
202
+
203
+ if details:
204
+ enhanced = self.enhancer.enhance_check_result(
205
+ check_name, status, message
206
+ )
207
+ check_data.update(enhanced.to_dict())
208
+
209
+ output["checks"][check_name] = check_data
210
+
211
+ return json.dumps(output, indent=2, default=str)
212
+
213
+ def _append_details(self, lines: list, detail: HealthCheckDetail) -> None:
214
+ """Append detailed information to output lines."""
215
+ if detail.recommendations:
216
+ lines.append(" Recommendations:")
217
+ for rec in detail.recommendations:
218
+ lines.append(f" • {rec}")
219
+
220
+ if detail.fix_commands:
221
+ lines.append(" Fix Commands:")
222
+ for cmd_info in detail.fix_commands:
223
+ lines.append(f" • {cmd_info['description']}")
224
+ lines.append(f" {cmd_info['command']}")
225
+
226
+ def _get_next_steps(
227
+ self, enhanced_checks: dict[str, HealthCheckDetail]
228
+ ) -> list[str]:
229
+ """Determine recommended next steps based on check results."""
230
+ next_steps = []
231
+
232
+ for enhanced in enhanced_checks.values():
233
+ if enhanced.status == HealthStatus.UNHEALTHY:
234
+ if enhanced.fix_commands:
235
+ next_steps.append(f"Run: {enhanced.fix_commands[0]['command']}")
236
+ break
237
+
238
+ if not next_steps:
239
+ # Check for degraded items
240
+ for enhanced in enhanced_checks.values():
241
+ if enhanced.status == HealthStatus.DEGRADED:
242
+ if enhanced.fix_commands:
243
+ next_steps.append(
244
+ f"Consider: {enhanced.fix_commands[0]['command']}"
245
+ )
246
+
247
+ if not next_steps:
248
+ next_steps.append("All systems healthy! Run 'roadmap health' regularly.")
249
+
250
+ return next_steps