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,110 @@
1
+ """Normalizes remote issue state for comparison with local state.
2
+
3
+ Extracted from SyncStateComparator to separate state normalization logic.
4
+ """
5
+
6
+ from datetime import datetime
7
+ from typing import Any
8
+
9
+ from structlog import get_logger
10
+
11
+ logger = get_logger(__name__)
12
+
13
+
14
+ class RemoteStateNormalizer:
15
+ """Normalizes remote issue state for sync operations."""
16
+
17
+ @staticmethod
18
+ def normalize_remote_state(remote: Any) -> dict[str, Any] | None:
19
+ """Normalize remote issue state to standard format.
20
+
21
+ Args:
22
+ remote: Remote issue object or dict
23
+
24
+ Returns:
25
+ Normalized state dict, or None if invalid
26
+ """
27
+ if remote is None:
28
+ return None
29
+
30
+ try:
31
+ if isinstance(remote, dict):
32
+ return remote.copy()
33
+
34
+ # Convert object to dict
35
+ state = {}
36
+
37
+ # Extract standard fields
38
+ for field in [
39
+ "id",
40
+ "status",
41
+ "title",
42
+ "content",
43
+ "labels",
44
+ "assignee",
45
+ "milestone",
46
+ ]:
47
+ value = getattr(remote, field, None)
48
+ if value is not None:
49
+ state[field] = value
50
+
51
+ # Handle timestamps
52
+ for ts_field in ["updated_at", "created_at"]:
53
+ if hasattr(remote, ts_field):
54
+ state[ts_field] = getattr(remote, ts_field)
55
+
56
+ return state if state else None
57
+
58
+ except Exception as e:
59
+ logger.debug(
60
+ "normalize_remote_state_failed",
61
+ error=str(e),
62
+ remote_type=type(remote).__name__,
63
+ )
64
+ return None
65
+
66
+ @staticmethod
67
+ def extract_timestamp(
68
+ remote: Any, field_name: str = "updated_at"
69
+ ) -> datetime | None:
70
+ """Extract timestamp from remote issue.
71
+
72
+ Args:
73
+ remote: Remote issue object or dict
74
+ field_name: Name of timestamp field (default: "updated_at")
75
+
76
+ Returns:
77
+ Datetime object, or None if not found/invalid
78
+ """
79
+ if remote is None:
80
+ return None
81
+
82
+ try:
83
+ if isinstance(remote, dict):
84
+ value = remote.get(field_name)
85
+ else:
86
+ value = getattr(remote, field_name, None)
87
+
88
+ if value is None:
89
+ return None
90
+
91
+ if isinstance(value, datetime):
92
+ return value
93
+
94
+ if isinstance(value, str):
95
+ # Try ISO format
96
+ try:
97
+ return datetime.fromisoformat(value.replace("Z", "+00:00"))
98
+ except (ValueError, AttributeError):
99
+ return None
100
+
101
+ return None
102
+
103
+ except Exception as e:
104
+ logger.debug(
105
+ "normalize_state_failed",
106
+ operation="normalize_state",
107
+ error=str(e),
108
+ action="Returning None",
109
+ )
110
+ return None
@@ -0,0 +1,51 @@
1
+ """Retry policy with exponential backoff strategy."""
2
+
3
+ import time
4
+
5
+
6
+ class RetryPolicy:
7
+ """Simple exponential backoff retry policy.
8
+
9
+ Example:
10
+ rp = RetryPolicy(max_retries=3, base_delay=0.5, factor=2.0)
11
+ for attempt in range(1, rp.max_retries + 1):
12
+ try:
13
+ ...
14
+ except Exception:
15
+ if attempt == rp.max_retries:
16
+ raise
17
+ time.sleep(rp.get_backoff_delay(attempt))
18
+ """
19
+
20
+ def __init__(
21
+ self, max_retries: int = 3, base_delay: float = 0.5, factor: float = 2.0
22
+ ):
23
+ """Initialize RetryPolicy.
24
+
25
+ Args:
26
+ max_retries: Maximum number of retry attempts.
27
+ base_delay: Initial delay in seconds.
28
+ factor: Exponential factor for delay increase.
29
+ """
30
+ self.max_retries = max_retries
31
+ self.base_delay = base_delay
32
+ self.factor = factor
33
+
34
+ def get_backoff_delay(self, attempt: int) -> float:
35
+ """Get delay in seconds for a given attempt (1-based)."""
36
+ if attempt <= 1:
37
+ return 0.0
38
+ return self.base_delay * (self.factor ** (attempt - 1))
39
+
40
+ def sleep_for_attempt(self, attempt: int) -> None:
41
+ """Sleep for the appropriate backoff delay based on attempt number.
42
+
43
+ Args:
44
+ attempt: The attempt number (1-based).
45
+ """
46
+ delay = self.get_backoff_delay(attempt)
47
+ if delay > 0:
48
+ time.sleep(delay)
49
+
50
+
51
+ __all__ = ["RetryPolicy"]
@@ -0,0 +1,130 @@
1
+ """Base class for all health validators.
2
+
3
+ Provides consistent error handling, logging, and status reporting patterns
4
+ for health check validators throughout the application.
5
+
6
+ All validators should inherit from BaseValidator and implement:
7
+ - get_check_name(): Return unique check identifier
8
+ - perform_check(): Return (status, message) tuple
9
+
10
+ The base class handles error handling and logging automatically.
11
+ """
12
+
13
+ from abc import ABC, abstractmethod
14
+
15
+ from roadmap.common.logging import get_logger
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ class HealthStatus:
21
+ """Health status constants for validator responses."""
22
+
23
+ HEALTHY = "healthy"
24
+ DEGRADED = "degraded"
25
+ UNHEALTHY = "unhealthy"
26
+
27
+
28
+ class BaseValidator(ABC):
29
+ """Abstract base class for all health validators.
30
+
31
+ Provides:
32
+ - Consistent error handling and logging
33
+ - Standard status/message return format
34
+ - Simplified subclass implementation (business logic only)
35
+
36
+ Subclasses should override:
37
+ - get_check_name(): Return the unique name of this check
38
+ - perform_check(): Return (status, message) tuple or raise exceptions
39
+
40
+ The check() method wraps perform_check() with try/except, logging,
41
+ and error handling so subclasses focus on business logic only.
42
+
43
+ Example:
44
+ class RoadmapDirectoryValidator(BaseValidator):
45
+ @staticmethod
46
+ def get_check_name() -> str:
47
+ return "roadmap_directory"
48
+
49
+ @staticmethod
50
+ def perform_check() -> Tuple[str, str]:
51
+ roadmap_dir = Path(".roadmap")
52
+ if not roadmap_dir.exists():
53
+ return HealthStatus.DEGRADED, ".roadmap not found"
54
+
55
+ try:
56
+ test_file = roadmap_dir / ".test"
57
+ test_file.touch()
58
+ test_file.unlink()
59
+ except OSError:
60
+ return HealthStatus.DEGRADED, "Directory not writable"
61
+
62
+ return HealthStatus.HEALTHY, "Accessible and writable"
63
+
64
+ # Usage:
65
+ status, message = RoadmapDirectoryValidator.check()
66
+ """
67
+
68
+ @staticmethod
69
+ @abstractmethod
70
+ def get_check_name() -> str:
71
+ """Return the unique name of this check.
72
+
73
+ Returns:
74
+ Check name as string (e.g., "roadmap_directory", "git_repository")
75
+ """
76
+ pass
77
+
78
+ @staticmethod
79
+ @abstractmethod
80
+ def perform_check() -> tuple[str, str]:
81
+ """Perform the actual check logic.
82
+
83
+ Should return a tuple of (status, message) where:
84
+ - status is one of: HealthStatus.HEALTHY, .DEGRADED, .UNHEALTHY
85
+ - message is a human-readable description
86
+
87
+ May raise exceptions for error conditions. The check() method
88
+ will catch them and return UNHEALTHY status.
89
+
90
+ Returns:
91
+ Tuple of (status, message)
92
+
93
+ Raises:
94
+ Any exception on error conditions (will be caught and logged)
95
+ """
96
+ pass
97
+
98
+ @classmethod
99
+ def check(cls) -> tuple[str, str]:
100
+ """Execute the check with standard error handling and logging.
101
+
102
+ Wraps perform_check() with:
103
+ - Exception handling
104
+ - Automatic logging at appropriate levels
105
+ - Error context enrichment
106
+ - Consistent status reporting
107
+
108
+ Returns:
109
+ Tuple of (status, message)
110
+ """
111
+ try:
112
+ status, message = cls.perform_check()
113
+ logger.debug(
114
+ "health_check_completed",
115
+ check_name=cls.get_check_name(),
116
+ status=status,
117
+ )
118
+ return status, message
119
+ except Exception as e:
120
+ logger.error(
121
+ "health_check_failed",
122
+ check_name=cls.get_check_name(),
123
+ error=str(e),
124
+ error_type=type(e).__name__,
125
+ severity="system_error",
126
+ )
127
+ return (
128
+ HealthStatus.UNHEALTHY,
129
+ f"Error checking {cls.get_check_name()}: {e}",
130
+ )
@@ -0,0 +1,28 @@
1
+ """Data integrity validators for health checks."""
2
+
3
+ from ._utils import BackupScanResult, extract_issue_id
4
+ from .archivable_issues_validator import ArchivableIssuesValidator
5
+ from .archivable_milestones_validator import ArchivableMilestonesValidator
6
+ from .backup_validator import BackupValidator
7
+ from .data_integrity_validator import DataIntegrityValidator
8
+ from .duplicate_issues_validator import DuplicateIssuesValidator
9
+ from .duplicate_milestones_validator import DuplicateMilestonesValidator
10
+ from .folder_structure_validator import FolderStructureValidator
11
+ from .missing_headlines_validator import MissingHeadlinesValidator
12
+ from .orphaned_issues_validator import OrphanedIssuesValidator
13
+ from .orphaned_milestones_validator import OrphanedMilestonesValidator
14
+
15
+ __all__ = [
16
+ "BackupScanResult",
17
+ "extract_issue_id",
18
+ "DuplicateIssuesValidator",
19
+ "DuplicateMilestonesValidator",
20
+ "FolderStructureValidator",
21
+ "BackupValidator",
22
+ "ArchivableIssuesValidator",
23
+ "ArchivableMilestonesValidator",
24
+ "DataIntegrityValidator",
25
+ "OrphanedIssuesValidator",
26
+ "OrphanedMilestonesValidator",
27
+ "MissingHeadlinesValidator",
28
+ ]
@@ -0,0 +1,20 @@
1
+ """Shared utilities for validators."""
2
+
3
+ import re
4
+ from typing import TypedDict
5
+
6
+
7
+ class BackupScanResult(TypedDict):
8
+ """Type definition for backup scan results."""
9
+
10
+ files_to_delete: list[dict]
11
+ total_size_bytes: int
12
+
13
+
14
+ def extract_issue_id(filename: str) -> str | None:
15
+ """Extract issue ID from filename (first part before the dashes and title).
16
+
17
+ Issue IDs are 8 hex characters.
18
+ """
19
+ match = re.match(r"^([a-f0-9]{8})", filename)
20
+ return match.group(1) if match else None
@@ -0,0 +1,71 @@
1
+ """Validator for archivable issues."""
2
+
3
+ from roadmap.common.logging import get_logger
4
+ from roadmap.common.utils.timezone_utils import now_utc
5
+ from roadmap.core.services.validator_base import HealthStatus
6
+
7
+ logger = get_logger(__name__)
8
+
9
+
10
+ class ArchivableIssuesValidator:
11
+ """Validator for archivable issues."""
12
+
13
+ @staticmethod
14
+ def scan_for_archivable_issues(core, threshold_days: int = 30) -> list[dict]:
15
+ """Scan for issues that should be archived (closed >threshold_days ago).
16
+
17
+ Returns a list of issue dicts with id, title, status, closed date, and days_since_close.
18
+ """
19
+ archivable = []
20
+
21
+ try:
22
+ issues = core.issue_service.list_issues()
23
+ now = now_utc()
24
+
25
+ for issue in issues:
26
+ # Check if issue is closed (by status or by completed_date)
27
+ if issue.status.value == "closed" or issue.actual_end_date:
28
+ close_date = issue.actual_end_date or now
29
+ days_since_close = (now - close_date).days
30
+
31
+ if days_since_close > threshold_days:
32
+ archivable.append(
33
+ {
34
+ "id": issue.id,
35
+ "title": issue.title,
36
+ "status": issue.status.value,
37
+ "closed_date": close_date.isoformat()
38
+ if close_date
39
+ else None,
40
+ "days_since_close": days_since_close,
41
+ }
42
+ )
43
+ except Exception as e:
44
+ logger.debug("scan_archivable_issues_error", error=str(e))
45
+
46
+ return archivable
47
+
48
+ @staticmethod
49
+ def check_archivable_issues(core) -> tuple[str, str]:
50
+ """Check for archivable issues.
51
+
52
+ Returns:
53
+ Tuple of (status, message) describing the health check result
54
+ """
55
+ try:
56
+ archivable = ArchivableIssuesValidator.scan_for_archivable_issues(core)
57
+
58
+ if not archivable:
59
+ logger.debug("health_check_archivable_issues", status="none")
60
+ return HealthStatus.HEALTHY, "No issues to archive"
61
+
62
+ message = (
63
+ f"ℹ️ {len(archivable)} issue(s) eligible for archival "
64
+ "(closed >30 days ago): Consider archiving old issues"
65
+ )
66
+ logger.info("health_check_archivable_issues", count=len(archivable))
67
+ return HealthStatus.DEGRADED, message
68
+
69
+ except Exception as e:
70
+ logger.debug("health_check_archivable_issues_failed", error=str(e))
71
+ return HealthStatus.HEALTHY, "Could not check for archivable issues"
@@ -0,0 +1,69 @@
1
+ """Validator for archivable milestones."""
2
+
3
+ from datetime import UTC, datetime
4
+
5
+ from roadmap.common.logging import get_logger
6
+ from roadmap.core.services.validator_base import HealthStatus
7
+
8
+ logger = get_logger(__name__)
9
+
10
+
11
+ class ArchivableMilestonesValidator:
12
+ """Validator for archivable milestones."""
13
+
14
+ @staticmethod
15
+ def scan_for_archivable_milestones(core, threshold_days: int = 14) -> list[dict]:
16
+ """Scan for milestones that should be archived (completed >threshold_days ago).
17
+
18
+ Returns a list of milestone dicts with name, status, closed date, issue count, and days_since_close.
19
+ """
20
+ archivable = []
21
+
22
+ try:
23
+ milestones = core.milestone_service.list_milestones()
24
+ now = datetime.now(UTC)
25
+
26
+ for milestone in milestones:
27
+ if milestone.status.value == "closed" and milestone.closed_at:
28
+ days_since_close = (now - milestone.closed_at).days
29
+
30
+ if days_since_close > threshold_days:
31
+ archivable.append(
32
+ {
33
+ "name": milestone.name,
34
+ "status": milestone.status.value,
35
+ "closed_date": milestone.closed_at.isoformat(),
36
+ "days_since_close": days_since_close,
37
+ }
38
+ )
39
+ except Exception as e:
40
+ logger.debug("scan_archivable_milestones_error", error=str(e))
41
+
42
+ return archivable
43
+
44
+ @staticmethod
45
+ def check_archivable_milestones(core) -> tuple[str, str]:
46
+ """Check for archivable milestones.
47
+
48
+ Returns:
49
+ Tuple of (status, message) describing the health check result
50
+ """
51
+ try:
52
+ archivable = ArchivableMilestonesValidator.scan_for_archivable_milestones(
53
+ core
54
+ )
55
+
56
+ if not archivable:
57
+ logger.debug("health_check_archivable_milestones", status="none")
58
+ return HealthStatus.HEALTHY, "No milestones to archive"
59
+
60
+ message = (
61
+ f"ℹ️ {len(archivable)} milestone(s) eligible for archival "
62
+ "(closed >14 days ago): Consider archiving old milestones"
63
+ )
64
+ logger.info("health_check_archivable_milestones", count=len(archivable))
65
+ return HealthStatus.DEGRADED, message
66
+
67
+ except Exception as e:
68
+ logger.debug("health_check_archivable_milestones_failed", error=str(e))
69
+ return HealthStatus.HEALTHY, "Could not check for archivable milestones"
@@ -0,0 +1,89 @@
1
+ """Validator for old backup files."""
2
+
3
+ from pathlib import Path
4
+
5
+ from roadmap.common.logging import get_logger
6
+
7
+ from . import BackupScanResult
8
+
9
+ logger = get_logger(__name__)
10
+
11
+
12
+ class BackupValidator:
13
+ """Validator for old backup files."""
14
+
15
+ @staticmethod
16
+ def scan_for_old_backups(backups_dir: Path, keep: int = 10) -> BackupScanResult:
17
+ """Scan for old backup files that could be deleted.
18
+
19
+ Returns a dict with:
20
+ - 'files_to_delete': List of backup files that exceed the keep threshold
21
+ - 'total_size_bytes': Total size of files that could be deleted (int)
22
+ """
23
+ result: BackupScanResult = {"files_to_delete": [], "total_size_bytes": 0}
24
+
25
+ if not backups_dir.exists():
26
+ return result
27
+
28
+ backup_files = list(backups_dir.glob("*.backup.md"))
29
+ if not backup_files:
30
+ return result
31
+
32
+ # Group backups by issue ID
33
+ backups_by_issue = {}
34
+ for backup_file in backup_files:
35
+ parts = backup_file.stem.split("_")
36
+ issue_key = "_".join(parts[:-1])
37
+
38
+ if issue_key not in backups_by_issue:
39
+ backups_by_issue[issue_key] = []
40
+
41
+ stat = backup_file.stat()
42
+ backups_by_issue[issue_key].append(
43
+ {
44
+ "path": backup_file,
45
+ "mtime": stat.st_mtime,
46
+ "size": stat.st_size,
47
+ }
48
+ )
49
+
50
+ # Find files that exceed keep threshold
51
+ for _issue_key, backups in backups_by_issue.items():
52
+ backups.sort(key=lambda x: x["mtime"], reverse=True)
53
+
54
+ for idx, backup in enumerate(backups):
55
+ if idx >= keep:
56
+ result["files_to_delete"].append(backup)
57
+ result["total_size_bytes"] += backup["size"]
58
+
59
+ return result
60
+
61
+ @staticmethod
62
+ def check_old_backups() -> tuple[str, str]:
63
+ """Check for old backup files.
64
+
65
+ Returns:
66
+ Tuple of (status, message) describing the health check result
67
+ """
68
+ try:
69
+ from roadmap.core.services.validator_base import HealthStatus
70
+
71
+ backups_dir = Path(".roadmap/backups")
72
+ old_backups = BackupValidator.scan_for_old_backups(backups_dir)
73
+
74
+ if not old_backups["files_to_delete"]:
75
+ logger.debug("health_check_old_backups", status="none")
76
+ return HealthStatus.HEALTHY, "No old backups to clean up"
77
+
78
+ count = len(old_backups["files_to_delete"])
79
+ size_mb = old_backups["total_size_bytes"] / (1024 * 1024)
80
+ message = (
81
+ f"ℹ️ {count} old backup file(s) could be deleted "
82
+ f"(~{size_mb:.1f} MB saved): Run 'roadmap cleanup' to remove"
83
+ )
84
+ logger.info("health_check_old_backups", count=count, size_mb=size_mb)
85
+ return HealthStatus.HEALTHY, message
86
+
87
+ except Exception as e:
88
+ logger.debug("health_check_old_backups_failed", error=str(e))
89
+ return HealthStatus.HEALTHY, "Could not check for old backups"
@@ -0,0 +1,84 @@
1
+ """Validator for data integrity."""
2
+
3
+ from pathlib import Path
4
+
5
+ from roadmap.common.logging import get_logger
6
+ from roadmap.core.services.validator_base import HealthStatus
7
+ from roadmap.infrastructure.persistence_gateway import PersistenceGateway
8
+
9
+ logger = get_logger(__name__)
10
+
11
+
12
+ class DataIntegrityValidator:
13
+ """Validator for data integrity."""
14
+
15
+ @staticmethod
16
+ def scan_for_data_integrity_issues(
17
+ issues_dir: Path,
18
+ ) -> dict[str, list[str]]:
19
+ """Scan for data integrity issues (orphaned/missing files).
20
+
21
+ Returns a dict with:
22
+ - 'malformed_files': List of files that couldn't be parsed
23
+ """
24
+ result = {"malformed_files": []}
25
+
26
+ if not issues_dir.exists():
27
+ return result
28
+
29
+ # Scan all issue files recursively
30
+ for issue_file in issues_dir.rglob("*.md"):
31
+ if ".backup" in issue_file.name:
32
+ continue
33
+
34
+ try:
35
+ PersistenceGateway.parse_issue_file(issue_file)
36
+ except Exception:
37
+ # File couldn't be parsed
38
+ result["malformed_files"].append(
39
+ str(issue_file.relative_to(issues_dir))
40
+ )
41
+
42
+ return result
43
+
44
+ @staticmethod
45
+ def check_data_integrity() -> tuple[str, str]:
46
+ """Check data integrity.
47
+
48
+ Returns:
49
+ Tuple of (status, message) describing the health check result
50
+ """
51
+ try:
52
+ issues_dir = Path(".roadmap/issues")
53
+ if not issues_dir.exists():
54
+ return (
55
+ HealthStatus.HEALTHY,
56
+ "Issues directory not found (not initialized yet)",
57
+ )
58
+
59
+ integrity_issues = DataIntegrityValidator.scan_for_data_integrity_issues(
60
+ issues_dir
61
+ )
62
+
63
+ if not integrity_issues["malformed_files"]:
64
+ logger.debug("health_check_data_integrity", status="healthy")
65
+ return HealthStatus.HEALTHY, "No data integrity issues found"
66
+
67
+ message = (
68
+ f"⚠️ {len(integrity_issues['malformed_files'])} malformed file(s) detected: "
69
+ "These files couldn't be parsed - manual review required"
70
+ )
71
+ logger.warning(
72
+ "health_check_data_integrity",
73
+ count=len(integrity_issues["malformed_files"]),
74
+ severity="operational",
75
+ )
76
+ return HealthStatus.DEGRADED, message
77
+
78
+ except Exception as e:
79
+ logger.error(
80
+ "health_check_data_integrity_failed",
81
+ error=str(e),
82
+ severity="system_error",
83
+ )
84
+ return HealthStatus.UNHEALTHY, f"Error checking data integrity: {e}"