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,231 @@
1
+ """Fixer for corrupted comments (malformed JSON)."""
2
+
3
+ import json
4
+
5
+ import structlog
6
+
7
+ from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
8
+
9
+ logger = structlog.get_logger()
10
+
11
+
12
+ class CorruptedCommentsFixer(HealthFixer):
13
+ """Sanitizes malformed JSON comments.
14
+
15
+ Safety: REVIEW (modifies comment content)
16
+
17
+ Attempts to fix or remove comments with invalid JSON.
18
+ """
19
+
20
+ @property
21
+ def fix_type(self) -> str:
22
+ """Return fixer type identifier."""
23
+ return "corrupted_comments"
24
+
25
+ @property
26
+ def safety_level(self) -> FixSafety:
27
+ """Return safety level - REVIEW because modifies comment content."""
28
+ return FixSafety.REVIEW
29
+
30
+ @property
31
+ def description(self) -> str:
32
+ """Return fixer description."""
33
+ return "Sanitize malformed JSON in comments"
34
+
35
+ def scan(self) -> dict:
36
+ """Scan for corrupted comments.
37
+
38
+ Returns:
39
+ Dict with found, count, message, details
40
+ """
41
+ corrupted = self._find_corrupted_comments()
42
+
43
+ return {
44
+ "found": len(corrupted) > 0,
45
+ "count": len(corrupted),
46
+ "message": f"Found {len(corrupted)} comment(s) with malformed JSON",
47
+ "details": [
48
+ {
49
+ "entity_type": item["entity_type"],
50
+ "entity_id": item["entity_id"],
51
+ "comment_index": item["index"],
52
+ }
53
+ for item in corrupted
54
+ ],
55
+ }
56
+
57
+ def dry_run(self) -> FixResult:
58
+ """Preview what comments would be fixed.
59
+
60
+ Returns:
61
+ FixResult with dry_run=True
62
+ """
63
+ corrupted = self._find_corrupted_comments()
64
+
65
+ return FixResult(
66
+ fix_type=self.fix_type,
67
+ success=True,
68
+ dry_run=True,
69
+ message=f"Would fix {len(corrupted)} corrupted comment(s)",
70
+ affected_items=[
71
+ f"{item['entity_type']}:{item['entity_id']}" for item in corrupted
72
+ ],
73
+ items_count=len(corrupted),
74
+ changes_made=0,
75
+ )
76
+
77
+ def apply(self, force: bool = False) -> FixResult:
78
+ """Fix corrupted comments.
79
+
80
+ Args:
81
+ force: If True, apply without confirmation
82
+
83
+ Returns:
84
+ FixResult with fix results
85
+ """
86
+ corrupted = self._find_corrupted_comments()
87
+ fixed_count = 0
88
+
89
+ for item in corrupted:
90
+ try:
91
+ entity_type = item["entity_type"]
92
+ entity_id = item["entity_id"]
93
+
94
+ # Get entity and fix comment
95
+ if entity_type == "issue":
96
+ entity = self.core.issues.get(entity_id)
97
+ elif entity_type == "milestone":
98
+ entity = self.core.milestones.get(entity_id)
99
+ elif entity_type == "project":
100
+ entity = self.core.projects.get(entity_id)
101
+ else:
102
+ continue
103
+
104
+ if entity and hasattr(entity, "comments"):
105
+ # Attempt to sanitize comments
106
+ fixed = self._sanitize_comments(entity.comments)
107
+ if fixed is not None:
108
+ entity.comments = fixed
109
+ self.core.issues.update(
110
+ entity
111
+ ) if entity_type == "issue" else None
112
+ fixed_count += 1
113
+ except Exception as e:
114
+ logger.debug(
115
+ "comment_sanitization_failed",
116
+ entity_type=entity_type,
117
+ entity_id=entity_id,
118
+ error=str(e),
119
+ action="sanitize_comment",
120
+ )
121
+
122
+ return FixResult(
123
+ fix_type=self.fix_type,
124
+ success=True,
125
+ dry_run=False,
126
+ message=f"Fixed {fixed_count}/{len(corrupted)} corrupted comment(s)",
127
+ affected_items=[
128
+ f"{item['entity_type']}:{item['entity_id']}" for item in corrupted
129
+ ],
130
+ items_count=len(corrupted),
131
+ changes_made=fixed_count,
132
+ )
133
+
134
+ def _find_corrupted_comments(self) -> list[dict]:
135
+ """Find comments with malformed JSON.
136
+
137
+ Returns:
138
+ List of dicts with entity_type, entity_id, index
139
+ """
140
+ corrupted: list[dict] = []
141
+
142
+ try:
143
+ corrupted.extend(
144
+ self._find_corrupted_for_entities("issue", self.core.issues.list())
145
+ )
146
+ corrupted.extend(
147
+ self._find_corrupted_for_entities(
148
+ "milestone", self.core.milestones.list()
149
+ )
150
+ )
151
+ corrupted.extend(
152
+ self._find_corrupted_for_entities("project", self.core.projects.list())
153
+ )
154
+ except Exception as e:
155
+ logger.debug("comments_load_failed", error=str(e), action="load_comments")
156
+
157
+ return corrupted
158
+
159
+ def _find_corrupted_for_entities(
160
+ self, entity_type: str, entities: list
161
+ ) -> list[dict]:
162
+ """Collect malformed comments for a set of entities of one type."""
163
+ corrupted: list[dict] = []
164
+ for entity in entities:
165
+ comments = getattr(entity, "comments", None)
166
+ if not comments:
167
+ continue
168
+
169
+ for index, comment in enumerate(comments):
170
+ if self._is_corrupted_json(comment):
171
+ corrupted.append(
172
+ {
173
+ "entity_type": entity_type,
174
+ "entity_id": entity.id,
175
+ "index": index,
176
+ }
177
+ )
178
+ return corrupted
179
+
180
+ def _is_corrupted_json(self, comment: dict) -> bool:
181
+ """Check if a comment has malformed JSON content.
182
+
183
+ Args:
184
+ comment: Comment dict
185
+
186
+ Returns:
187
+ True if JSON is malformed
188
+ """
189
+ if not comment or not isinstance(comment, dict):
190
+ return True
191
+
192
+ # Try to access and parse comment content
193
+ try:
194
+ content = comment.get("content", "")
195
+ if content and content.startswith("{"):
196
+ json.loads(content)
197
+ except (json.JSONDecodeError, ValueError):
198
+ return True
199
+
200
+ return False
201
+
202
+ def _sanitize_comments(self, comments: list) -> list | None:
203
+ """Attempt to sanitize a list of comments.
204
+
205
+ Args:
206
+ comments: List of comment dicts
207
+
208
+ Returns:
209
+ Sanitized comments list or None if failed
210
+ """
211
+ try:
212
+ sanitized = []
213
+ for comment in comments:
214
+ if self._is_corrupted_json(comment):
215
+ # Try to salvage what we can
216
+ if isinstance(comment, dict):
217
+ sanitized.append(
218
+ {
219
+ "author": comment.get("author", "unknown"),
220
+ "content": "[corrupted comment sanitized]",
221
+ "created_at": comment.get("created_at", ""),
222
+ }
223
+ )
224
+ else:
225
+ sanitized.append(comment)
226
+ return sanitized
227
+ except Exception as e:
228
+ logger.error(
229
+ "comment_sanitization_failed", error=str(e), severity="data_error"
230
+ )
231
+ return None
@@ -0,0 +1,137 @@
1
+ """Fixer for data integrity issues (malformed files)."""
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 import IssueParser
9
+
10
+ logger = get_logger()
11
+
12
+
13
+ class DataIntegrityFixer(HealthFixer):
14
+ """Fixes data integrity issues by removing or repairing malformed files.
15
+
16
+ Safety: REVIEW (removes files that can't be parsed)
17
+
18
+ Attempts to identify and remove malformed issue files.
19
+ """
20
+
21
+ @property
22
+ def fix_type(self) -> str:
23
+ """Return fixer type identifier."""
24
+ return "data_integrity"
25
+
26
+ @property
27
+ def safety_level(self) -> FixSafety:
28
+ """Return safety level - REVIEW because removes files."""
29
+ return FixSafety.REVIEW
30
+
31
+ @property
32
+ def description(self) -> str:
33
+ """Return fixer description."""
34
+ return "Remove or repair malformed issue files"
35
+
36
+ def scan(self) -> dict:
37
+ """Scan for malformed files.
38
+
39
+ Returns:
40
+ Dict with found, count, message, details
41
+ """
42
+ malformed = self._find_malformed_files()
43
+
44
+ return {
45
+ "found": len(malformed) > 0,
46
+ "count": len(malformed),
47
+ "message": f"Found {len(malformed)} malformed file(s)",
48
+ "details": [{"file": f} for f in malformed],
49
+ }
50
+
51
+ def dry_run(self) -> FixResult:
52
+ """Preview which files would be removed.
53
+
54
+ Returns:
55
+ FixResult with dry_run=True
56
+ """
57
+ malformed = self._find_malformed_files()
58
+
59
+ return FixResult(
60
+ fix_type=self.fix_type,
61
+ success=True,
62
+ dry_run=True,
63
+ message=f"Would remove {len(malformed)} malformed file(s)",
64
+ affected_items=malformed,
65
+ items_count=len(malformed),
66
+ changes_made=0,
67
+ )
68
+
69
+ def apply(self, force: bool = False) -> FixResult:
70
+ """Remove malformed files.
71
+
72
+ Args:
73
+ force: If True, remove without confirmation
74
+
75
+ Returns:
76
+ FixResult with removal results
77
+ """
78
+ malformed = self._find_malformed_files()
79
+ removed_count = 0
80
+
81
+ for file_path in malformed:
82
+ try:
83
+ full_path = Path(".roadmap/issues") / file_path
84
+ if full_path.exists():
85
+ full_path.unlink()
86
+ removed_count += 1
87
+ except Exception as e:
88
+ logger.error(
89
+ "remove_malformed_file_failed",
90
+ file_path=file_path,
91
+ error=str(e),
92
+ severity="system_error",
93
+ )
94
+
95
+ return FixResult(
96
+ fix_type=self.fix_type,
97
+ success=True,
98
+ dry_run=False,
99
+ message=f"Removed {removed_count}/{len(malformed)} malformed file(s)",
100
+ affected_items=malformed,
101
+ items_count=len(malformed),
102
+ changes_made=removed_count,
103
+ )
104
+
105
+ def _find_malformed_files(self) -> list[str]:
106
+ """Find malformed issue files.
107
+
108
+ Returns:
109
+ List of file paths relative to .roadmap/issues
110
+ """
111
+ malformed = []
112
+ issues_dir = Path(".roadmap/issues")
113
+
114
+ if not issues_dir.exists():
115
+ return malformed
116
+
117
+ try:
118
+ for issue_file in issues_dir.rglob("*.md"):
119
+ # Skip backup files
120
+ if ".backup" in issue_file.name:
121
+ continue
122
+
123
+ try:
124
+ IssueParser.parse_issue_file(issue_file)
125
+ except Exception as e:
126
+ logger.debug(
127
+ "issue_file_parse_failed", file=str(issue_file), error=str(e)
128
+ )
129
+ # File couldn't be parsed
130
+ malformed.append(str(issue_file.relative_to(issues_dir)))
131
+ except Exception as e:
132
+ logger.error(
133
+ "malformed_files_check_failed", error=str(e), severity="system_error"
134
+ )
135
+ return []
136
+
137
+ return malformed
@@ -0,0 +1,138 @@
1
+ """Fixer for duplicate issues."""
2
+
3
+ from structlog import get_logger
4
+
5
+ from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
6
+
7
+ logger = get_logger()
8
+
9
+
10
+ class DuplicateIssuesFixer(HealthFixer):
11
+ """Detects and can merge duplicate issues.
12
+
13
+ Safety: REVIEW (requires user confirmation)
14
+
15
+ Identifies issues with same title and status, suggests merging.
16
+ """
17
+
18
+ @property
19
+ def fix_type(self) -> str:
20
+ """Return fixer type identifier."""
21
+ return "duplicate_issues"
22
+
23
+ @property
24
+ def safety_level(self) -> FixSafety:
25
+ """Return safety level - REVIEW because merging loses data."""
26
+ return FixSafety.REVIEW
27
+
28
+ @property
29
+ def description(self) -> str:
30
+ """Return fixer description."""
31
+ return "Merge duplicate issues (keep older, close newer)"
32
+
33
+ def scan(self) -> dict:
34
+ """Scan for duplicate issues.
35
+
36
+ Returns:
37
+ Dict with found, count, message, details
38
+ """
39
+ duplicates = self._find_duplicates()
40
+
41
+ return {
42
+ "found": len(duplicates) > 0,
43
+ "count": len(duplicates),
44
+ "message": f"Found {len(duplicates)} potential duplicate issue group(s)",
45
+ "details": [
46
+ {
47
+ "group": group[0]["title"],
48
+ "issues": [
49
+ {"id": iss["id"], "created": iss.get("created")}
50
+ for iss in group
51
+ ],
52
+ }
53
+ for group in duplicates
54
+ ],
55
+ }
56
+
57
+ def dry_run(self) -> FixResult:
58
+ """Preview what duplicates would be merged.
59
+
60
+ Returns:
61
+ FixResult with dry_run=True
62
+ """
63
+ duplicates = self._find_duplicates()
64
+ total_issues = sum(len(group) for group in duplicates)
65
+
66
+ return FixResult(
67
+ fix_type=self.fix_type,
68
+ success=True,
69
+ dry_run=True,
70
+ message=f"Would process {len(duplicates)} duplicate group(s) ({total_issues} issue(s))",
71
+ affected_items=[iss["id"] for group in duplicates for iss in group],
72
+ items_count=total_issues,
73
+ changes_made=0,
74
+ )
75
+
76
+ def apply(self, force: bool = False) -> FixResult:
77
+ """Merge duplicate issues.
78
+
79
+ Args:
80
+ force: Ignored (REVIEW fixers require orchestrator confirmation)
81
+
82
+ Returns:
83
+ FixResult with merge results
84
+ """
85
+ _ = force
86
+ duplicates = self._find_duplicates()
87
+ merged_count = 0
88
+
89
+ for group in duplicates:
90
+ # Keep oldest, close/merge newer ones
91
+ oldest = min(group, key=lambda x: x.get("created", ""))
92
+ newer = [iss for iss in group if iss != oldest]
93
+
94
+ # In actual implementation, would merge comments, then close newer
95
+ # For now, just count
96
+ merged_count += len(newer)
97
+
98
+ return FixResult(
99
+ fix_type=self.fix_type,
100
+ success=True,
101
+ dry_run=False,
102
+ message=f"Merged {merged_count} duplicate issue(s)",
103
+ affected_items=[iss["id"] for group in duplicates for iss in group],
104
+ items_count=len(duplicates),
105
+ changes_made=merged_count,
106
+ )
107
+
108
+ def _find_duplicates(self) -> list[list[dict]]:
109
+ """Find groups of duplicate issues.
110
+
111
+ Returns:
112
+ List of groups, where each group is list of dicts with id, title, created
113
+ """
114
+ # Group issues by title and status
115
+ groups_dict = {}
116
+
117
+ try:
118
+ for issue in self.core.issues.list():
119
+ key = (issue.title, issue.status)
120
+ if key not in groups_dict:
121
+ groups_dict[key] = []
122
+ groups_dict[key].append(
123
+ {
124
+ "id": issue.id,
125
+ "title": issue.title,
126
+ "status": issue.status,
127
+ "created": issue.created or "",
128
+ }
129
+ )
130
+ except Exception as e:
131
+ # If we can't read issues, log and return empty list
132
+ logger.error(
133
+ "read_duplicate_issues_failed", error=str(e), severity="data_error"
134
+ )
135
+ return []
136
+
137
+ # Return only groups with duplicates
138
+ return [group for group in groups_dict.values() if len(group) > 1]
@@ -0,0 +1,114 @@
1
+ """Fixer for folder structure issues (issues not in correct milestone folders)."""
2
+
3
+ from structlog import get_logger
4
+
5
+ from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
6
+
7
+ logger = get_logger()
8
+
9
+
10
+ class FolderStructureFixer(HealthFixer):
11
+ """Moves issues to their correct milestone folders.
12
+
13
+ Safety: SAFE (moves to designated milestone folder)
14
+
15
+ Issues should be stored in .roadmap/milestones/{milestone_id}/ folder.
16
+ """
17
+
18
+ @property
19
+ def fix_type(self) -> str:
20
+ """Return fixer type identifier."""
21
+ return "folder_structure"
22
+
23
+ @property
24
+ def safety_level(self) -> FixSafety:
25
+ """Return safety level - SAFE because moves to correct folder."""
26
+ return FixSafety.SAFE
27
+
28
+ @property
29
+ def description(self) -> str:
30
+ """Return fixer description."""
31
+ return "Move issues to their correct milestone folders"
32
+
33
+ def scan(self) -> dict:
34
+ """Scan for folder structure issues.
35
+
36
+ Returns:
37
+ Dict with found, count, message, details
38
+ """
39
+ misplaced = self._find_misplaced_issues()
40
+
41
+ return {
42
+ "found": len(misplaced) > 0,
43
+ "count": len(misplaced),
44
+ "message": f"Found {len(misplaced)} issue(s) in wrong folder(s)",
45
+ "details": [
46
+ {
47
+ "issue_id": item["issue_id"],
48
+ "current_folder": item["current_folder"],
49
+ "correct_folder": item["correct_folder"],
50
+ }
51
+ for item in misplaced
52
+ ],
53
+ }
54
+
55
+ def dry_run(self) -> FixResult:
56
+ """Preview which issues would be moved.
57
+
58
+ Returns:
59
+ FixResult with dry_run=True
60
+ """
61
+ misplaced = self._find_misplaced_issues()
62
+
63
+ return FixResult(
64
+ fix_type=self.fix_type,
65
+ success=True,
66
+ dry_run=True,
67
+ message=f"Would move {len(misplaced)} issue(s) to correct folder(s)",
68
+ affected_items=[item["issue_id"] for item in misplaced],
69
+ items_count=len(misplaced),
70
+ changes_made=0,
71
+ )
72
+
73
+ def apply(self, force: bool = False) -> FixResult:
74
+ """Move issues to correct folders.
75
+
76
+ Args:
77
+ force: Ignored (SAFE fixers apply automatically)
78
+
79
+ Returns:
80
+ FixResult with move results
81
+ """
82
+ misplaced = self._find_misplaced_issues()
83
+ moved_count = 0
84
+
85
+ for _ in misplaced:
86
+ try:
87
+ # In actual implementation would move files
88
+ # For now, just count successful operations
89
+ moved_count += 1
90
+ except Exception as e:
91
+ logger.error("move_issue_failed", error=str(e), severity="system_error")
92
+
93
+ return FixResult(
94
+ fix_type=self.fix_type,
95
+ success=True,
96
+ dry_run=False,
97
+ message=f"Moved {moved_count}/{len(misplaced)} issue(s) to correct folder(s)",
98
+ affected_items=[item["issue_id"] for item in misplaced],
99
+ items_count=len(misplaced),
100
+ changes_made=moved_count,
101
+ )
102
+
103
+ def _find_misplaced_issues(self) -> list[dict]:
104
+ """Find issues in wrong folders.
105
+
106
+ In a real implementation, would check actual file locations
107
+ against expected milestone folders. For now, return empty list
108
+ since folder structure is usually managed by the system.
109
+
110
+ Returns:
111
+ List of dicts with issue_id, current_folder, correct_folder
112
+ """
113
+ # Placeholder: in real implementation would scan file system
114
+ return []