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,162 @@
1
+ """Fixer for milestone files that fail to parse (invisible to CLI)."""
2
+
3
+ from pathlib import Path
4
+
5
+ from structlog import get_logger
6
+
7
+ from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
8
+ from roadmap.adapters.persistence.parser.milestone import MilestoneParser
9
+
10
+ logger = get_logger()
11
+
12
+
13
+ class MilestoneParseErrorFixer(HealthFixer):
14
+ """Detects milestone files that exist on disk but fail to parse.
15
+
16
+ Safety: REVIEW (regenerates files from minimal defaults, discarding
17
+ unrecoverable YAML content)
18
+
19
+ A milestone file that fails to parse is silently excluded from
20
+ ``milestone list`` and ``milestone view``, causing confusing
21
+ inconsistencies (issues are queryable by milestone name but the
22
+ milestone itself is "not found"). This fixer surfaces those files
23
+ so the user is not left guessing why a milestone disappeared.
24
+
25
+ The ``apply()`` fix regenerates each broken file with sensible
26
+ defaults derived from the filename, which replicates the manual
27
+ workaround of running ``roadmap milestone create``.
28
+ """
29
+
30
+ @property
31
+ def fix_type(self) -> str:
32
+ """Return fixer type identifier."""
33
+ return "milestone_parse_errors"
34
+
35
+ @property
36
+ def safety_level(self) -> FixSafety:
37
+ """Return safety level - REVIEW because we overwrite files."""
38
+ return FixSafety.REVIEW
39
+
40
+ @property
41
+ def description(self) -> str:
42
+ """Return fixer description."""
43
+ return "Detect milestone files that fail to parse and are invisible to the CLI"
44
+
45
+ def scan(self) -> dict:
46
+ """Scan for milestone files that cannot be parsed.
47
+
48
+ Returns:
49
+ Dict with found, count, message, details
50
+ """
51
+ broken = self._find_broken_milestone_files()
52
+
53
+ return {
54
+ "found": len(broken) > 0,
55
+ "count": len(broken),
56
+ "message": f"Found {len(broken)} milestone file(s) that fail to parse",
57
+ "details": broken,
58
+ }
59
+
60
+ def dry_run(self) -> FixResult:
61
+ """Preview which milestone files would be regenerated.
62
+
63
+ Returns:
64
+ FixResult with dry_run=True
65
+ """
66
+ broken = self._find_broken_milestone_files()
67
+
68
+ return FixResult(
69
+ fix_type=self.fix_type,
70
+ success=True,
71
+ dry_run=True,
72
+ message=f"Would regenerate {len(broken)} milestone file(s) from defaults",
73
+ affected_items=[item["file"] for item in broken],
74
+ items_count=len(broken),
75
+ changes_made=0,
76
+ )
77
+
78
+ def apply(self, force: bool = False) -> FixResult:
79
+ """Regenerate broken milestone files with minimal valid defaults.
80
+
81
+ Each broken file is overwritten with a new milestone whose ``name``
82
+ is derived from the filename stem, preserving the original file
83
+ location. This replicates the manual workaround of recreating the
84
+ milestone via ``roadmap milestone create``.
85
+
86
+ Args:
87
+ force: Ignored (safety handled by REVIEW level and orchestrator).
88
+
89
+ Returns:
90
+ FixResult
91
+ """
92
+ broken = self._find_broken_milestone_files()
93
+ fixed_count = 0
94
+ failed_items: list[str] = []
95
+
96
+ for item in broken:
97
+ file_path = Path(item["file"])
98
+ milestone_name = item["name"]
99
+ try:
100
+ milestone = self.core.milestones.create(
101
+ name=milestone_name,
102
+ headline="",
103
+ )
104
+ if milestone:
105
+ fixed_count += 1
106
+ else:
107
+ failed_items.append(str(file_path))
108
+ except Exception as e:
109
+ logger.error(
110
+ "milestone_parse_error_fix_failed",
111
+ file=str(file_path),
112
+ error=str(e),
113
+ severity="data_error",
114
+ )
115
+ failed_items.append(str(file_path))
116
+
117
+ return FixResult(
118
+ fix_type=self.fix_type,
119
+ success=len(failed_items) == 0,
120
+ dry_run=False,
121
+ message=f"Regenerated {fixed_count}/{len(broken)} broken milestone file(s)",
122
+ affected_items=[
123
+ item["file"] for item in broken if item["file"] not in failed_items
124
+ ],
125
+ items_count=len(broken),
126
+ changes_made=fixed_count,
127
+ )
128
+
129
+ def _find_broken_milestone_files(self) -> list[dict]:
130
+ """Find milestone files that raise exceptions during parsing.
131
+
132
+ Returns:
133
+ List of dicts with 'file', 'name', 'error' keys
134
+ """
135
+ broken: list[dict] = []
136
+
137
+ try:
138
+ milestones_dir = Path(".roadmap/milestones").resolve()
139
+ if not milestones_dir.exists():
140
+ return broken
141
+
142
+ for md_file in sorted(milestones_dir.glob("*.md")):
143
+ if ".backup" in md_file.name:
144
+ continue
145
+ try:
146
+ MilestoneParser.parse_milestone_file(md_file)
147
+ except Exception as e:
148
+ broken.append(
149
+ {
150
+ "file": str(md_file),
151
+ "name": md_file.stem,
152
+ "error": f"{type(e).__name__}: {e}",
153
+ }
154
+ )
155
+ except Exception as e:
156
+ logger.error(
157
+ "find_broken_milestone_files_failed",
158
+ error=str(e),
159
+ severity="data_error",
160
+ )
161
+
162
+ return broken
@@ -0,0 +1,211 @@
1
+ """Fixer for issues with invalid milestone references."""
2
+
3
+ from difflib import get_close_matches
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
+
10
+ logger = get_logger()
11
+
12
+
13
+ class MilestoneValidationFixer(HealthFixer):
14
+ """Validates that all issue milestones point to existing milestones.
15
+
16
+ Safety: REVIEW (may reassign issues to different milestones)
17
+
18
+ Issues with invalid milestone references are either:
19
+ 1. Moved to backlog if no similar milestone found
20
+ 2. Updated to closest matching milestone if found
21
+ """
22
+
23
+ @property
24
+ def fix_type(self) -> str:
25
+ """Return fixer type identifier."""
26
+ return "milestone_validation"
27
+
28
+ @property
29
+ def safety_level(self) -> FixSafety:
30
+ """Return safety level - REVIEW because we may reassign milestones."""
31
+ return FixSafety.REVIEW
32
+
33
+ @property
34
+ def description(self) -> str:
35
+ """Return fixer description."""
36
+ return "Validate and fix invalid milestone references in issues"
37
+
38
+ def scan(self) -> dict:
39
+ """Scan for issues with invalid milestone references.
40
+
41
+ Returns:
42
+ Dict with found, count, message, details
43
+ """
44
+ invalid = self._find_invalid_milestones()
45
+
46
+ return {
47
+ "found": len(invalid) > 0,
48
+ "count": len(invalid),
49
+ "message": f"Found {len(invalid)} issue(s) with invalid milestone(s)",
50
+ "details": [
51
+ {
52
+ "id": iss["id"],
53
+ "title": iss["title"],
54
+ "invalid_milestone": iss["milestone"],
55
+ }
56
+ for iss in invalid
57
+ ],
58
+ }
59
+
60
+ def dry_run(self) -> FixResult:
61
+ """Preview which issues would be fixed.
62
+
63
+ Returns:
64
+ FixResult with dry_run=True
65
+ """
66
+ invalid = self._find_invalid_milestones()
67
+
68
+ affected = []
69
+ for iss in invalid:
70
+ suggestion = self._get_suggestion(iss["milestone"])
71
+ if suggestion:
72
+ affected.append(f"{iss['id']} ({iss['milestone']} → {suggestion})")
73
+ else:
74
+ affected.append(f"{iss['id']} ({iss['milestone']} → backlog)")
75
+
76
+ return FixResult(
77
+ fix_type=self.fix_type,
78
+ success=True,
79
+ dry_run=True,
80
+ message=f"Would fix {len(invalid)} issue(s) with invalid milestone(s)",
81
+ affected_items=affected,
82
+ items_count=len(invalid),
83
+ changes_made=0,
84
+ )
85
+
86
+ def apply(self, force: bool = False) -> FixResult:
87
+ """Fix issues with invalid milestone references.
88
+
89
+ Args:
90
+ force: Ignored (SAFE fixers apply automatically)
91
+
92
+ Returns:
93
+ FixResult with fix results
94
+ """
95
+ _ = force
96
+ invalid = self._find_invalid_milestones()
97
+ fixed_count = 0
98
+ failed_items = []
99
+
100
+ for issue_data in invalid:
101
+ try:
102
+ issue_id = issue_data["id"]
103
+ old_milestone = issue_data["milestone"]
104
+
105
+ # Try to find a suggestion
106
+ suggestion = self._get_suggestion(old_milestone)
107
+ if not suggestion:
108
+ # No similar milestone, move to backlog
109
+ suggestion = "backlog"
110
+
111
+ # Assign to suggested milestone
112
+ if self.core.issues.assign_to_milestone(issue_id, suggestion):
113
+ fixed_count += 1
114
+ else:
115
+ failed_items.append(issue_id)
116
+ except Exception as e:
117
+ logger.error(
118
+ "fix_invalid_milestone_failed",
119
+ issue_id=issue_data["id"],
120
+ error=str(e),
121
+ severity="data_error",
122
+ )
123
+ failed_items.append(issue_data["id"])
124
+
125
+ return FixResult(
126
+ fix_type=self.fix_type,
127
+ success=len(failed_items) == 0,
128
+ dry_run=False,
129
+ message=f"Fixed {fixed_count}/{len(invalid)} issue(s) with invalid milestone(s)",
130
+ affected_items=[
131
+ iss["id"] for iss in invalid if iss["id"] not in failed_items
132
+ ],
133
+ items_count=len(invalid),
134
+ changes_made=fixed_count,
135
+ )
136
+
137
+ def _find_invalid_milestones(self) -> list[dict]:
138
+ """Find issues with milestone references that don't exist.
139
+
140
+ Returns:
141
+ List of dicts with id, title, milestone
142
+ """
143
+ invalid = []
144
+
145
+ try:
146
+ # Get all available milestone names
147
+ milestones_dir = Path(".roadmap/milestones").resolve()
148
+ if not milestones_dir.exists():
149
+ return invalid
150
+
151
+ available_milestones = {f.stem for f in milestones_dir.glob("*.md")}
152
+
153
+ # Check all issues
154
+ for issue in self.core.issues.list():
155
+ if not issue.milestone:
156
+ # No milestone is valid (goes to backlog)
157
+ continue
158
+
159
+ # "backlog" is a valid pseudo-milestone (no file on disk)
160
+ if issue.milestone.lower() == "backlog":
161
+ continue
162
+
163
+ # Convert milestone name to safe filename for comparison
164
+ safe_name = "".join(
165
+ c for c in issue.milestone if c.isalnum() or c in (" ", "-", "_")
166
+ ).strip()
167
+ safe_name = safe_name.replace(" ", "-").lower()
168
+
169
+ if safe_name not in available_milestones:
170
+ invalid.append(
171
+ {
172
+ "id": issue.id,
173
+ "title": issue.title,
174
+ "milestone": issue.milestone,
175
+ }
176
+ )
177
+
178
+ except Exception as e:
179
+ logger.error(
180
+ "find_invalid_milestones_failed", error=str(e), severity="data_error"
181
+ )
182
+ return []
183
+
184
+ return invalid
185
+
186
+ def _get_suggestion(self, invalid_milestone: str) -> str | None:
187
+ """Find a suggested milestone for an invalid one.
188
+
189
+ Args:
190
+ invalid_milestone: The invalid milestone name
191
+
192
+ Returns:
193
+ Suggested milestone name, or None if no good match found
194
+ """
195
+ try:
196
+ milestones_dir = Path(".roadmap/milestones").resolve()
197
+ if not milestones_dir.exists():
198
+ return None
199
+
200
+ available = [f.stem for f in milestones_dir.glob("*.md")]
201
+
202
+ # Find close matches
203
+ matches = get_close_matches(invalid_milestone, available, n=1, cutoff=0.6)
204
+ return matches[0] if matches else None
205
+ except Exception as e:
206
+ logger.debug(
207
+ "get_milestone_suggestion_failed",
208
+ milestone=invalid_milestone,
209
+ error=str(e),
210
+ )
211
+ return None
@@ -0,0 +1,135 @@
1
+ """Fixer for old backup files."""
2
+
3
+ from datetime import UTC, datetime, timedelta
4
+ from pathlib import Path
5
+
6
+ from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
7
+
8
+
9
+ class OldBackupsFixer(HealthFixer):
10
+ """Removes old backup files to free up storage.
11
+
12
+ Safety: SAFE (automatically applies)
13
+
14
+ Removes backup files older than 90 days.
15
+ """
16
+
17
+ BACKUP_THRESHOLD_DAYS = 90
18
+
19
+ @property
20
+ def fix_type(self) -> str:
21
+ """Return fixer type identifier."""
22
+ return "old_backups"
23
+
24
+ @property
25
+ def safety_level(self) -> FixSafety:
26
+ """Return safety level - SAFE because backups are not primary data."""
27
+ return FixSafety.SAFE
28
+
29
+ @property
30
+ def description(self) -> str:
31
+ """Return fixer description."""
32
+ return f"Delete backup files older than {self.BACKUP_THRESHOLD_DAYS} days"
33
+
34
+ def scan(self) -> dict:
35
+ """Scan for old backup files.
36
+
37
+ Returns:
38
+ Dict with found, count, message, details
39
+ """
40
+ old_backups = self._find_old_backups()
41
+
42
+ return {
43
+ "found": len(old_backups) > 0,
44
+ "count": len(old_backups),
45
+ "message": f"Found {len(old_backups)} old backup file(s) to clean up",
46
+ "details": [
47
+ {"path": str(p), "age_days": self._get_age_days(p)} for p in old_backups
48
+ ],
49
+ }
50
+
51
+ def dry_run(self) -> FixResult:
52
+ """Preview which backups would be deleted.
53
+
54
+ Returns:
55
+ FixResult with dry_run=True
56
+ """
57
+ old_backups = self._find_old_backups()
58
+
59
+ return FixResult(
60
+ fix_type=self.fix_type,
61
+ success=True,
62
+ dry_run=True,
63
+ message=f"Would delete {len(old_backups)} old backup file(s)",
64
+ affected_items=[str(p) for p in old_backups],
65
+ items_count=len(old_backups),
66
+ changes_made=0,
67
+ )
68
+
69
+ def apply(self, force: bool = False) -> FixResult:
70
+ """Delete old backup files.
71
+
72
+ Args:
73
+ force: Ignored (SAFE fixers apply automatically)
74
+
75
+ Returns:
76
+ FixResult with results of deletion
77
+ """
78
+ _ = force
79
+ old_backups = self._find_old_backups()
80
+ deleted_count = 0
81
+ failed_items = []
82
+
83
+ for backup_path in old_backups:
84
+ try:
85
+ backup_path.unlink()
86
+ deleted_count += 1
87
+ except Exception:
88
+ failed_items.append(str(backup_path))
89
+
90
+ success = len(failed_items) == 0
91
+
92
+ return FixResult(
93
+ fix_type=self.fix_type,
94
+ success=success,
95
+ dry_run=False,
96
+ message=f"Deleted {deleted_count}/{len(old_backups)} old backup file(s)",
97
+ affected_items=[str(p) for p in old_backups],
98
+ items_count=len(old_backups),
99
+ changes_made=deleted_count,
100
+ )
101
+
102
+ def _find_old_backups(self) -> list[Path]:
103
+ """Find backup files older than threshold.
104
+
105
+ Returns:
106
+ List of Path objects for old backups
107
+ """
108
+ old_backups = []
109
+ backup_dir = Path(".roadmap/backups")
110
+
111
+ if not backup_dir.exists():
112
+ return old_backups
113
+
114
+ now = datetime.now(UTC)
115
+ threshold = now - timedelta(days=self.BACKUP_THRESHOLD_DAYS)
116
+
117
+ for backup_file in backup_dir.glob("*.db"):
118
+ mtime = datetime.fromtimestamp(backup_file.stat().st_mtime)
119
+ if mtime < threshold:
120
+ old_backups.append(backup_file)
121
+
122
+ return sorted(old_backups)
123
+
124
+ def _get_age_days(self, path: Path) -> int:
125
+ """Get age of file in days.
126
+
127
+ Args:
128
+ path: Path to file
129
+
130
+ Returns:
131
+ Age in days
132
+ """
133
+ mtime = datetime.fromtimestamp(path.stat().st_mtime)
134
+ age = datetime.now(UTC) - mtime
135
+ return age.days