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 normalizing issue labels to alphabetically sorted order."""
2
+
3
+ from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
4
+
5
+
6
+ class LabelNormalizationFixer(HealthFixer):
7
+ """Normalizes issue labels to alphabetically sorted order.
8
+
9
+ Safety: SAFE (just reorders labels, no data loss)
10
+
11
+ GitHub may return labels in different order than local storage, causing false
12
+ "needs push" detection during sync. This fixer ensures labels are always
13
+ stored in alphabetical order for consistency.
14
+ """
15
+
16
+ @property
17
+ def fix_type(self) -> str:
18
+ """Return fixer type identifier."""
19
+ return "label_normalization"
20
+
21
+ @property
22
+ def safety_level(self) -> FixSafety:
23
+ """Return safety level - SAFE because we're just reordering labels."""
24
+ return FixSafety.SAFE
25
+
26
+ @property
27
+ def description(self) -> str:
28
+ """Return fixer description."""
29
+ return "Normalize issue labels to alphabetical order"
30
+
31
+ def scan(self) -> dict:
32
+ """Scan for issues with unsorted labels.
33
+
34
+ Returns:
35
+ Dict with found, count, message, details
36
+ """
37
+ unsorted_issues = self._find_unsorted_labels()
38
+
39
+ return {
40
+ "found": len(unsorted_issues) > 0,
41
+ "count": len(unsorted_issues),
42
+ "message": f"Found {len(unsorted_issues)} issue(s) with unsorted labels",
43
+ "details": [
44
+ {
45
+ "id": iss["id"],
46
+ "title": iss["title"],
47
+ "current": iss["current_labels"],
48
+ "sorted": iss["sorted_labels"],
49
+ }
50
+ for iss in unsorted_issues
51
+ ],
52
+ }
53
+
54
+ def dry_run(self) -> FixResult:
55
+ """Preview which issues would have labels sorted.
56
+
57
+ Returns:
58
+ FixResult with dry_run=True
59
+ """
60
+ unsorted_issues = self._find_unsorted_labels()
61
+
62
+ affected = [
63
+ f"{iss['id']}: {iss['current_labels']} → {iss['sorted_labels']}"
64
+ for iss in unsorted_issues
65
+ ]
66
+
67
+ return FixResult(
68
+ fix_type=self.fix_type,
69
+ success=True,
70
+ dry_run=True,
71
+ message=f"Would normalize {len(unsorted_issues)} issue label(s)",
72
+ affected_items=affected,
73
+ items_count=len(unsorted_issues),
74
+ changes_made=0,
75
+ )
76
+
77
+ def apply(self, force: bool = False) -> FixResult:
78
+ """Sort labels in all issues to alphabetical order.
79
+
80
+ Args:
81
+ force: If True, skip confirmation prompt
82
+
83
+ Returns:
84
+ FixResult with results of applying fixes
85
+ """
86
+ unsorted_issues = self._find_unsorted_labels()
87
+
88
+ if not unsorted_issues:
89
+ return FixResult(
90
+ fix_type=self.fix_type,
91
+ success=True,
92
+ dry_run=False,
93
+ message="No issues found with unsorted labels",
94
+ affected_items=[],
95
+ items_count=0,
96
+ changes_made=0,
97
+ )
98
+
99
+ affected = []
100
+ changes = 0
101
+
102
+ try:
103
+ for issue_data in unsorted_issues:
104
+ issue = issue_data["issue"]
105
+ sorted_labels = issue_data["sorted_labels"]
106
+
107
+ # Save the issue with sorted labels via the update API
108
+ self.core.issues.update(issue.id, labels=sorted_labels)
109
+
110
+ affected.append(f"{issue.id} ({len(sorted_labels)} labels sorted)")
111
+ changes += 1
112
+
113
+ return FixResult(
114
+ fix_type=self.fix_type,
115
+ success=True,
116
+ dry_run=False,
117
+ message=f"Normalized labels in {changes} issue(s)",
118
+ affected_items=affected,
119
+ items_count=len(unsorted_issues),
120
+ changes_made=changes,
121
+ )
122
+
123
+ except Exception as e:
124
+ return FixResult(
125
+ fix_type=self.fix_type,
126
+ success=False,
127
+ dry_run=False,
128
+ message=f"Failed to normalize labels: {str(e)}",
129
+ affected_items=affected,
130
+ items_count=len(unsorted_issues),
131
+ changes_made=changes,
132
+ )
133
+
134
+ def _find_unsorted_labels(self) -> list[dict]:
135
+ """Find all issues with unsorted labels.
136
+
137
+ Returns:
138
+ List of dicts with issue data and label info
139
+ """
140
+ unsorted_issues = []
141
+
142
+ # Get all local issues including archived
143
+ all_issues = self.core.issues.list_all_including_archived()
144
+
145
+ for issue in all_issues:
146
+ if not issue.labels:
147
+ continue
148
+
149
+ # Check if labels are sorted
150
+ sorted_labels = sorted(issue.labels)
151
+ if issue.labels != sorted_labels:
152
+ unsorted_issues.append(
153
+ {
154
+ "id": issue.id,
155
+ "issue": issue,
156
+ "title": issue.title,
157
+ "current_labels": issue.labels,
158
+ "sorted_labels": sorted_labels,
159
+ }
160
+ )
161
+
162
+ return unsorted_issues
@@ -0,0 +1,181 @@
1
+ """Fixer for normalizing milestone names to safe format in issue metadata."""
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
+
9
+ logger = get_logger()
10
+
11
+
12
+ class MilestoneNameNormalizationFixer(HealthFixer):
13
+ """Normalizes issue milestone metadata to match milestone filenames.
14
+
15
+ Safety: SAFE (just normalizes names to match actual milestones)
16
+
17
+ Issues may have milestone names like 'v.0.8.0' but milestone files are 'v080.md'.
18
+ This fixer updates issue metadata to use the safe filename format.
19
+ """
20
+
21
+ @property
22
+ def fix_type(self) -> str:
23
+ """Return fixer type identifier."""
24
+ return "milestone_name_normalization"
25
+
26
+ @property
27
+ def safety_level(self) -> FixSafety:
28
+ """Return safety level - SAFE because we're just normalizing names."""
29
+ return FixSafety.SAFE
30
+
31
+ @property
32
+ def description(self) -> str:
33
+ """Return fixer description."""
34
+ return "Normalize milestone names to match milestone filenames"
35
+
36
+ def scan(self) -> dict:
37
+ """Scan for issues with non-normalized milestone names.
38
+
39
+ Returns:
40
+ Dict with found, count, message, details
41
+ """
42
+ mismatched = self._find_mismatched_milestone_names()
43
+
44
+ return {
45
+ "found": len(mismatched) > 0,
46
+ "count": len(mismatched),
47
+ "message": f"Found {len(mismatched)} issue(s) with non-normalized milestone name(s)",
48
+ "details": [
49
+ {
50
+ "id": iss["id"],
51
+ "title": iss["title"],
52
+ "current": iss["current_name"],
53
+ "should_be": iss["normalized_name"],
54
+ }
55
+ for iss in mismatched
56
+ ],
57
+ }
58
+
59
+ def dry_run(self) -> FixResult:
60
+ """Preview which issues would be fixed.
61
+
62
+ Returns:
63
+ FixResult with dry_run=True
64
+ """
65
+ mismatched = self._find_mismatched_milestone_names()
66
+
67
+ affected = [
68
+ f"{iss['id']} ({iss['current_name']} → {iss['normalized_name']})"
69
+ for iss in mismatched
70
+ ]
71
+
72
+ return FixResult(
73
+ fix_type=self.fix_type,
74
+ success=True,
75
+ dry_run=True,
76
+ message=f"Would normalize {len(mismatched)} issue milestone name(s)",
77
+ affected_items=affected,
78
+ items_count=len(mismatched),
79
+ changes_made=0,
80
+ )
81
+
82
+ def apply(self, force: bool = False) -> FixResult:
83
+ """Normalize milestone names in issue metadata.
84
+
85
+ Args:
86
+ force: Ignored (SAFE fixers apply automatically)
87
+
88
+ Returns:
89
+ FixResult with fix results
90
+ """
91
+ _ = force
92
+ mismatched = self._find_mismatched_milestone_names()
93
+ fixed_count = 0
94
+ failed_items = []
95
+
96
+ for issue_data in mismatched:
97
+ try:
98
+ issue_id = issue_data["id"]
99
+ normalized_name = issue_data["normalized_name"]
100
+
101
+ # Assign to normalized milestone name
102
+ if self.core.issues.assign_to_milestone(issue_id, normalized_name):
103
+ fixed_count += 1
104
+ else:
105
+ failed_items.append(issue_id)
106
+ except Exception as e:
107
+ logger.error(
108
+ "normalize_milestone_failed",
109
+ issue_id=issue_data["id"],
110
+ error=str(e),
111
+ severity="data_error",
112
+ )
113
+ failed_items.append(issue_data["id"])
114
+
115
+ return FixResult(
116
+ fix_type=self.fix_type,
117
+ success=len(failed_items) == 0,
118
+ dry_run=False,
119
+ message=f"Normalized {fixed_count}/{len(mismatched)} issue milestone name(s)",
120
+ affected_items=[
121
+ iss["id"] for iss in mismatched if iss["id"] not in failed_items
122
+ ],
123
+ items_count=len(mismatched),
124
+ changes_made=fixed_count,
125
+ )
126
+
127
+ def _find_mismatched_milestone_names(self) -> list[dict]:
128
+ """Find issues where milestone name doesn't match safe filename.
129
+
130
+ Returns:
131
+ List of dicts with id, title, current_name, normalized_name
132
+ """
133
+ mismatched = []
134
+
135
+ try:
136
+ milestones_dir = Path(".roadmap/milestones").resolve()
137
+ if not milestones_dir.exists():
138
+ return mismatched
139
+
140
+ # Build map of display names to safe names
141
+ safe_name_to_display = {}
142
+ for f in milestones_dir.glob("*.md"):
143
+ safe_name = f.stem
144
+ # Try to find what display name might map to this
145
+ safe_name_to_display[safe_name] = safe_name
146
+
147
+ # Check all issues
148
+ for issue in self.core.issues.list():
149
+ if not issue.milestone:
150
+ continue
151
+
152
+ # Convert issue's milestone to safe name
153
+ safe_name = "".join(
154
+ c for c in issue.milestone if c.isalnum() or c in (" ", "-", "_")
155
+ ).strip()
156
+ safe_name = safe_name.replace(" ", "-").lower()
157
+
158
+ # Check if milestone file exists with this safe name
159
+ milestone_file = milestones_dir / f"{safe_name}.md"
160
+ if not milestone_file.exists():
161
+ # Milestone doesn't exist, skip
162
+ continue
163
+
164
+ # If the safe name differs from the issue's milestone, it needs normalizing
165
+ if safe_name != issue.milestone:
166
+ mismatched.append(
167
+ {
168
+ "id": issue.id,
169
+ "title": issue.title,
170
+ "current_name": issue.milestone,
171
+ "normalized_name": safe_name,
172
+ }
173
+ )
174
+
175
+ except Exception as e:
176
+ logger.error(
177
+ "find_mismatched_milestones_failed", error=str(e), severity="data_error"
178
+ )
179
+ return []
180
+
181
+ return mismatched
@@ -0,0 +1,244 @@
1
+ """Fixer for milestone naming convention compliance.
2
+
3
+ This fixer ensures that:
4
+ 1. Issue milestone values use actual milestone filenames (safe names)
5
+ 2. No display name variants (v.0.8.0) are used instead of safe names (v080)
6
+ 3. All naming is consistent across the system
7
+ """
8
+
9
+ from pathlib import Path
10
+
11
+ from structlog import get_logger
12
+
13
+ from roadmap.adapters.cli.health.fixer import FixResult, FixSafety, HealthFixer
14
+
15
+ logger = get_logger()
16
+
17
+
18
+ class MilestoneNamingComplianceFixer(HealthFixer):
19
+ """Fixes milestone names to match actual milestone filenames.
20
+
21
+ Converts display names like "v.0.8.0", "Future (Post-v1.0)" to their
22
+ actual safe milestone names like "v080", "future-post-v10".
23
+ """
24
+
25
+ DISPLAY_TO_SAFE_MILESTONE = {
26
+ "v.0.7.0": "v070",
27
+ "v.0.8.0": "v080",
28
+ "v.0.9.0": "v090",
29
+ "v.1.0.0": "v100",
30
+ "v.0.8": "v080",
31
+ "v0.8": "v080",
32
+ "v0.8.0": "v080",
33
+ "v.0.7": "v070",
34
+ "v0.7": "v070",
35
+ "v.0.9": "v090",
36
+ "v0.9": "v090",
37
+ "v.1.0": "v100",
38
+ "v1.0": "v100",
39
+ "Future (Post-v1.0)": "future-post-v10",
40
+ "Development": "backlog",
41
+ }
42
+
43
+ def __init__(self, core):
44
+ """Initialize fixer with core instance."""
45
+ super().__init__(core)
46
+ self.logger = logger
47
+
48
+ @property
49
+ def fix_type(self) -> str:
50
+ """Return fixer type identifier."""
51
+ return "milestone_naming_compliance"
52
+
53
+ @property
54
+ def safety_level(self) -> FixSafety:
55
+ """Return safety level - SAFE because we fix to actual milestone names."""
56
+ return FixSafety.SAFE
57
+
58
+ @property
59
+ def description(self) -> str:
60
+ """Return fixer description."""
61
+ return "Fix milestone naming to match actual milestone filenames"
62
+
63
+ def scan(self) -> dict:
64
+ """Scan for issues with non-compliant milestone names.
65
+
66
+ Returns:
67
+ Dict with found, count, message, details
68
+ """
69
+ non_compliant = self._find_non_compliant_milestones()
70
+
71
+ return {
72
+ "found": len(non_compliant) > 0,
73
+ "count": len(non_compliant),
74
+ "message": f"Found {len(non_compliant)} issue(s) with non-compliant milestone name(s)",
75
+ "details": [
76
+ {
77
+ "id": iss["id"],
78
+ "title": iss["title"],
79
+ "current": iss["current_milestone"],
80
+ "should_be": iss["safe_milestone"],
81
+ }
82
+ for iss in non_compliant
83
+ ],
84
+ }
85
+
86
+ def dry_run(self) -> FixResult:
87
+ """Preview which issues would be fixed.
88
+
89
+ Returns:
90
+ FixResult with dry_run=True
91
+ """
92
+ non_compliant = self._find_non_compliant_milestones()
93
+
94
+ affected = []
95
+ for iss in non_compliant:
96
+ affected.append(
97
+ f"{iss['id']} ({iss['current_milestone']} → {iss['safe_milestone']})"
98
+ )
99
+
100
+ return FixResult(
101
+ fix_type=self.fix_type,
102
+ success=True,
103
+ dry_run=True,
104
+ message=f"Would fix {len(non_compliant)} issue(s) with non-compliant milestone name(s)",
105
+ affected_items=affected,
106
+ items_count=len(non_compliant),
107
+ changes_made=0,
108
+ )
109
+
110
+ def apply(self, force: bool = False) -> FixResult:
111
+ """Apply milestone naming compliance fixes.
112
+
113
+ Args:
114
+ force: Not used for SAFE fixers
115
+
116
+ Returns:
117
+ FixResult with fix details
118
+ """
119
+ non_compliant = self._find_non_compliant_milestones()
120
+
121
+ if not non_compliant:
122
+ return FixResult(
123
+ fix_type=self.fix_type,
124
+ success=True,
125
+ dry_run=False,
126
+ message="No non-compliant milestone names found",
127
+ items_count=0,
128
+ changes_made=0,
129
+ )
130
+
131
+ affected = []
132
+ fixed = 0
133
+ failed = 0
134
+
135
+ for iss in non_compliant:
136
+ try:
137
+ # Update the issue file directly
138
+ file_path = Path(iss["file"])
139
+ content = file_path.read_text(encoding="utf-8")
140
+
141
+ # Replace milestone value
142
+ old_line = f"milestone: {iss['current_milestone']}"
143
+ new_line = f"milestone: {iss['safe_milestone']}"
144
+
145
+ updated_content = content.replace(old_line, new_line, 1)
146
+ file_path.write_text(updated_content, encoding="utf-8")
147
+
148
+ affected.append(
149
+ f"{iss['id']} ({iss['current_milestone']} → {iss['safe_milestone']})"
150
+ )
151
+ fixed += 1
152
+ except Exception as e:
153
+ self.logger.error(f"Failed to fix milestone for {iss['id']}: {e}")
154
+ failed += 1
155
+
156
+ return FixResult(
157
+ fix_type=self.fix_type,
158
+ success=failed == 0,
159
+ dry_run=False,
160
+ message=f"Fixed {fixed} issue(s) with non-compliant milestone name(s)",
161
+ affected_items=affected,
162
+ items_count=len(non_compliant),
163
+ changes_made=fixed,
164
+ )
165
+
166
+ def _find_non_compliant_milestones(self) -> list[dict]:
167
+ """Find all issues with non-compliant milestone names.
168
+
169
+ Returns:
170
+ List of issue dicts with: id, title, current_milestone, safe_milestone
171
+ """
172
+ issues: list[dict] = []
173
+ issues_dir = Path(".roadmap/issues").resolve()
174
+ milestones_dir = Path(".roadmap/milestones").resolve()
175
+
176
+ # Get all actual milestone files (safe names)
177
+ actual_milestones = self._load_actual_milestones(milestones_dir)
178
+
179
+ if not issues_dir.exists():
180
+ return issues
181
+
182
+ for issue_file in issues_dir.rglob("*.md"):
183
+ if issue_file.name.startswith("."):
184
+ continue
185
+
186
+ content = issue_file.read_text(encoding="utf-8")
187
+ current_milestone, title = self._parse_issue_frontmatter(content)
188
+
189
+ if not current_milestone:
190
+ continue
191
+
192
+ if current_milestone in actual_milestones:
193
+ continue
194
+
195
+ safe_milestone = self._resolve_safe_milestone(
196
+ current_milestone,
197
+ actual_milestones,
198
+ )
199
+
200
+ if safe_milestone != current_milestone:
201
+ issues.append(
202
+ {
203
+ "id": issue_file.stem.split("-")[0],
204
+ "title": title or issue_file.stem,
205
+ "file": str(issue_file),
206
+ "current_milestone": current_milestone,
207
+ "safe_milestone": safe_milestone,
208
+ }
209
+ )
210
+
211
+ return issues
212
+
213
+ def _load_actual_milestones(self, milestones_dir: Path) -> set[str]:
214
+ """Load safe milestone names from milestone filenames."""
215
+ if not milestones_dir.exists():
216
+ return set()
217
+ return {milestone_file.stem for milestone_file in milestones_dir.glob("*.md")}
218
+
219
+ def _parse_issue_frontmatter(self, content: str) -> tuple[str | None, str | None]:
220
+ """Extract milestone and title values from issue file frontmatter lines."""
221
+ current_milestone = None
222
+ title = None
223
+ for line in content.split("\n"):
224
+ if line.startswith("milestone:"):
225
+ current_milestone = line.replace("milestone:", "").strip()
226
+ elif line.startswith("title:"):
227
+ title = line.replace("title:", "").strip().strip("'\"")
228
+ return current_milestone, title
229
+
230
+ def _resolve_safe_milestone(
231
+ self,
232
+ current_milestone: str,
233
+ actual_milestones: set[str],
234
+ ) -> str:
235
+ """Resolve a milestone value to a safe milestone name."""
236
+ if not current_milestone or current_milestone.isspace():
237
+ return "backlog"
238
+
239
+ safe_milestone = self.DISPLAY_TO_SAFE_MILESTONE.get(
240
+ current_milestone, "backlog"
241
+ )
242
+ if safe_milestone not in actual_milestones:
243
+ return "backlog"
244
+ return safe_milestone