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,75 @@
1
+ """Validator for duplicate issues."""
2
+
3
+ from collections import defaultdict
4
+ from pathlib import Path
5
+
6
+ from roadmap.core.services.validator_base import BaseValidator, HealthStatus
7
+
8
+ from . import extract_issue_id
9
+
10
+
11
+ class DuplicateIssuesValidator(BaseValidator):
12
+ """Validator for duplicate issues."""
13
+
14
+ @staticmethod
15
+ def get_check_name() -> str:
16
+ """Get the name of this check.
17
+
18
+ Returns:
19
+ String identifier for the duplicate_issues check.
20
+ """
21
+ return "duplicate_issues"
22
+
23
+ @staticmethod
24
+ def scan_for_duplicate_issues(issues_dir: Path) -> dict[str, list[Path]]:
25
+ """Scan all issue files and identify duplicates by issue ID.
26
+
27
+ Returns a dict mapping issue_id -> list of file paths where duplicates exist (2+ occurrences).
28
+ """
29
+ issues_by_id = defaultdict(list)
30
+
31
+ # Scan all issue markdown files recursively
32
+ for issue_file in issues_dir.glob("**/*.md"):
33
+ # Skip backup files
34
+ if ".backup" in issue_file.name:
35
+ continue
36
+
37
+ issue_id = extract_issue_id(issue_file.name)
38
+ if issue_id:
39
+ issues_by_id[issue_id].append(issue_file)
40
+
41
+ # Return only duplicates (2+ occurrences)
42
+ duplicates = {
43
+ issue_id: files
44
+ for issue_id, files in issues_by_id.items()
45
+ if len(files) > 1
46
+ }
47
+
48
+ return duplicates
49
+
50
+ @staticmethod
51
+ def perform_check() -> tuple[str, str]:
52
+ """Check for duplicate issues.
53
+
54
+ Returns:
55
+ Tuple of (status, message) describing the health check result
56
+ """
57
+ issues_dir = Path(".roadmap/issues")
58
+ if not issues_dir.exists():
59
+ return (
60
+ HealthStatus.HEALTHY,
61
+ "Issues directory not found (not initialized yet)",
62
+ )
63
+
64
+ duplicates = DuplicateIssuesValidator.scan_for_duplicate_issues(issues_dir)
65
+
66
+ if not duplicates:
67
+ return HealthStatus.HEALTHY, "No duplicate issues found"
68
+
69
+ total_duplicates = sum(len(files) - 1 for files in duplicates.values())
70
+ message = (
71
+ f"⚠️ {len(duplicates)} issue ID(s) have duplicates "
72
+ f"({total_duplicates} duplicate files total): "
73
+ "Manual cleanup required"
74
+ )
75
+ return HealthStatus.DEGRADED, message
@@ -0,0 +1,141 @@
1
+ """Validator for duplicate milestones."""
2
+
3
+ from collections import defaultdict
4
+ from pathlib import Path
5
+
6
+ import structlog
7
+
8
+ from roadmap.core.services.validator_base import BaseValidator, HealthStatus
9
+ from roadmap.infrastructure.coordination.core import RoadmapCore
10
+
11
+ logger = structlog.get_logger()
12
+
13
+
14
+ class DuplicateMilestonesValidator(BaseValidator):
15
+ """Validator for duplicate milestones (by name and file)."""
16
+
17
+ @staticmethod
18
+ def get_check_name() -> str:
19
+ """Get the name of this check.
20
+
21
+ Returns:
22
+ String identifier for the duplicate_milestones check.
23
+ """
24
+ return "duplicate_milestones"
25
+
26
+ @staticmethod
27
+ def scan_for_duplicate_files(milestones_dir: Path) -> dict[str, list[Path]]:
28
+ """Scan all milestone files and identify duplicates by milestone name.
29
+
30
+ Returns a dict mapping milestone_name -> list of file paths where duplicates exist (2+ occurrences).
31
+ """
32
+ milestones_by_name = defaultdict(list)
33
+
34
+ # Scan all milestone markdown files
35
+ for milestone_file in milestones_dir.glob("*.md"):
36
+ # Skip backup files
37
+ if ".backup" in milestone_file.name:
38
+ continue
39
+
40
+ # Extract milestone name from filename (remove .md extension)
41
+ milestone_name = milestone_file.stem
42
+ milestones_by_name[milestone_name].append(milestone_file)
43
+
44
+ # Return only duplicates (2+ occurrences)
45
+ duplicates = {
46
+ name: files for name, files in milestones_by_name.items() if len(files) > 1
47
+ }
48
+
49
+ return duplicates
50
+
51
+ @staticmethod
52
+ def scan_for_duplicate_names(core) -> list[dict]:
53
+ """Scan for duplicate milestone names in the database.
54
+
55
+ Returns a list of duplicate milestone names with count.
56
+ """
57
+ duplicates = []
58
+
59
+ try:
60
+ all_milestones = core.milestones.list()
61
+
62
+ # Count occurrences of each name
63
+ name_counts = {}
64
+ for milestone in all_milestones:
65
+ name_counts[milestone.name] = name_counts.get(milestone.name, 0) + 1
66
+
67
+ # Find duplicates
68
+ for name, count in name_counts.items():
69
+ if count > 1:
70
+ duplicates.append(
71
+ {
72
+ "name": name,
73
+ "count": count,
74
+ }
75
+ )
76
+
77
+ except Exception as e:
78
+ logger.debug(
79
+ "scan_duplicate_names_failed",
80
+ operation="scan_for_duplicate_names",
81
+ error=str(e),
82
+ action="Returning empty duplicates",
83
+ )
84
+
85
+ return duplicates
86
+
87
+ @staticmethod
88
+ def perform_check() -> tuple[str, str]:
89
+ """Check for duplicate milestones (both file and name duplicates).
90
+
91
+ Returns:
92
+ Tuple of (status, message) describing the health check result
93
+ """
94
+ try:
95
+ milestones_dir = Path(".roadmap/milestones")
96
+ if not milestones_dir.exists():
97
+ return (
98
+ HealthStatus.HEALTHY,
99
+ "Milestones directory not found (not initialized yet)",
100
+ )
101
+
102
+ # Check for file duplicates
103
+ file_duplicates = DuplicateMilestonesValidator.scan_for_duplicate_files(
104
+ milestones_dir
105
+ )
106
+
107
+ # Check for name duplicates in database
108
+ core = RoadmapCore()
109
+ name_duplicates = DuplicateMilestonesValidator.scan_for_duplicate_names(
110
+ core
111
+ )
112
+
113
+ issues = []
114
+ if file_duplicates:
115
+ total_file_duplicates = sum(
116
+ len(files) - 1 for files in file_duplicates.values()
117
+ )
118
+ issues.append(
119
+ f"{len(file_duplicates)} milestone name(s) have duplicate files "
120
+ f"({total_file_duplicates} duplicate files total)"
121
+ )
122
+
123
+ if name_duplicates:
124
+ issues.append(
125
+ f"{len(name_duplicates)} milestone name(s) appear multiple times in database"
126
+ )
127
+
128
+ if not issues:
129
+ return HealthStatus.HEALTHY, "No duplicate milestones found"
130
+
131
+ message = f"⚠️ {'; '.join(issues)}: Manual cleanup required"
132
+ return HealthStatus.DEGRADED, message
133
+
134
+ except Exception as e:
135
+ logger.debug(
136
+ "duplicate_milestones_check_failed",
137
+ operation="perform_check",
138
+ error=str(e),
139
+ action="Returning healthy status",
140
+ )
141
+ return HealthStatus.HEALTHY, "Could not check for duplicate milestones"
@@ -0,0 +1,165 @@
1
+ """Validator for folder structure and issue placement."""
2
+
3
+ from pathlib import Path
4
+
5
+ from roadmap.common.logging import get_logger
6
+ from roadmap.core.services.validator_base import BaseValidator, HealthStatus
7
+
8
+ from . import extract_issue_id
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class FolderStructureValidator(BaseValidator):
14
+ """Validator for folder structure and issue placement."""
15
+
16
+ @staticmethod
17
+ def get_check_name() -> str:
18
+ """Get the name of this check.
19
+
20
+ Returns:
21
+ String identifier for the folder_structure check.
22
+ """
23
+ return "folder_structure"
24
+
25
+ @staticmethod
26
+ def _check_root_issues(issues_dir: Path, core, misplaced_list: list) -> None:
27
+ """Check root level issues for milestone placement issues."""
28
+ for issue_file in issues_dir.glob("*.md"):
29
+ if ".backup" in issue_file.name:
30
+ continue
31
+
32
+ try:
33
+ issue_id = extract_issue_id(issue_file.name)
34
+ if not issue_id:
35
+ continue
36
+
37
+ issue = core.issue_service.get_issue(issue_id)
38
+ if issue and issue.milestone:
39
+ milestone_folder = issues_dir / issue.milestone
40
+ if milestone_folder.exists():
41
+ misplaced_list.append(
42
+ {
43
+ "issue_id": issue.id,
44
+ "title": issue.title,
45
+ "current_location": str(issue_file),
46
+ "assigned_milestone": issue.milestone,
47
+ "expected_location": str(
48
+ milestone_folder / issue_file.name
49
+ ),
50
+ }
51
+ )
52
+ except Exception as e:
53
+ logger.debug(
54
+ "issue_parse_failed",
55
+ operation="parse_issue",
56
+ file=str(issue_file),
57
+ error=str(e),
58
+ action="Skipping issue",
59
+ )
60
+
61
+ @staticmethod
62
+ def _process_milestone_file(
63
+ issue_file: Path, core, milestone_folder: Path, orphaned_list: list
64
+ ) -> None:
65
+ """Process single file in milestone folder for orphaned issues.
66
+
67
+ Args:
68
+ issue_file: File to process
69
+ core: RoadmapCore instance
70
+ milestone_folder: Milestone folder path
71
+ orphaned_list: List to append orphaned issues to
72
+ """
73
+ if ".backup" in issue_file.name:
74
+ return
75
+
76
+ try:
77
+ issue_id = extract_issue_id(issue_file.name)
78
+ if not issue_id:
79
+ return
80
+
81
+ issue = core.issue_service.get_issue(issue_id)
82
+ if issue and not issue.milestone:
83
+ orphaned_list.append(
84
+ {
85
+ "issue_id": issue.id,
86
+ "title": issue.title,
87
+ "location": str(issue_file),
88
+ "folder": milestone_folder.name,
89
+ }
90
+ )
91
+ except Exception as e:
92
+ logger.debug(
93
+ "milestone_folder_check_failed",
94
+ operation="check_milestone_folders",
95
+ error=str(e),
96
+ action="Continuing to next folder",
97
+ )
98
+
99
+ @staticmethod
100
+ def _check_milestone_folders(issues_dir: Path, core, orphaned_list: list) -> None:
101
+ """Check milestone folders for orphaned issues."""
102
+ for milestone_folder in issues_dir.glob("*/"):
103
+ if not milestone_folder.is_dir() or milestone_folder.name.startswith("."):
104
+ continue
105
+
106
+ if milestone_folder.name == "backlog":
107
+ continue
108
+
109
+ for issue_file in milestone_folder.glob("*.md"):
110
+ FolderStructureValidator._process_milestone_file(
111
+ issue_file, core, milestone_folder, orphaned_list
112
+ )
113
+
114
+ @staticmethod
115
+ def scan_for_folder_structure_issues(
116
+ issues_dir: Path, core
117
+ ) -> dict[str, list[dict]]:
118
+ """Verify issues are in correct milestone folders.
119
+
120
+ Returns a dict of potential issues:
121
+ - 'misplaced': Issues in root when they belong in a milestone subfolder
122
+ - 'orphaned': Issues with milestone assignments but not in milestone folder
123
+ """
124
+ misplaced = []
125
+ orphaned = []
126
+
127
+ try:
128
+ FolderStructureValidator._check_root_issues(issues_dir, core, misplaced)
129
+ FolderStructureValidator._check_milestone_folders(
130
+ issues_dir, core, orphaned
131
+ )
132
+ except Exception as e:
133
+ logger.error(
134
+ "folder_structure_check_failed", error=str(e), severity="system_error"
135
+ )
136
+
137
+ return (
138
+ {
139
+ "misplaced": misplaced,
140
+ "orphaned": orphaned,
141
+ }
142
+ if (misplaced or orphaned)
143
+ else {}
144
+ )
145
+
146
+ @staticmethod
147
+ def perform_check() -> tuple[str, str]:
148
+ """Check folder structure and issue placement.
149
+
150
+ Note: This validator requires core access but is called without it.
151
+ For now, return HEALTHY to avoid blocking health checks.
152
+
153
+ Returns:
154
+ Tuple of (status, message) describing the health check result
155
+ """
156
+ issues_dir = Path(".roadmap/issues")
157
+ if not issues_dir.exists():
158
+ return (
159
+ HealthStatus.HEALTHY,
160
+ "Issues directory not found (not initialized yet)",
161
+ )
162
+
163
+ # Simplified check without core access for now
164
+ # Full check requires core.issue_service which we don't have in this context
165
+ return HealthStatus.HEALTHY, "Folder structure check (simplified)"
@@ -0,0 +1,28 @@
1
+ """Health status utilities for validators.
2
+
3
+ Common utilities for determining overall health status from individual checks.
4
+ """
5
+
6
+ from roadmap.core.services.validator_base import HealthStatus
7
+
8
+
9
+ def get_overall_status(checks: dict) -> str:
10
+ """Get overall status from all validation checks.
11
+
12
+ Args:
13
+ checks: Dictionary of check results with (status, message) tuples
14
+
15
+ Returns:
16
+ Overall status: 'healthy', 'degraded', or 'unhealthy'
17
+ """
18
+ if not checks:
19
+ return HealthStatus.UNHEALTHY
20
+
21
+ statuses = [status for status, _ in checks.values()]
22
+
23
+ if HealthStatus.UNHEALTHY in statuses:
24
+ return HealthStatus.UNHEALTHY
25
+ elif HealthStatus.DEGRADED in statuses:
26
+ return HealthStatus.DEGRADED
27
+ else:
28
+ return HealthStatus.HEALTHY
@@ -0,0 +1,146 @@
1
+ """Validator for milestone naming conventions."""
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+
7
+ class MilestoneNamingValidator:
8
+ """Validates milestone names against naming conventions.
9
+
10
+ Convention:
11
+ - Display names should match safe filenames (no conversion needed)
12
+ - Allowed characters: alphanumeric, hyphens, underscores
13
+ - Must start with alphanumeric
14
+ - Recommended patterns:
15
+ - Versions: v0-7-0, v0-8-0, v1-0-0 (hyphens, unambiguous)
16
+ - Sprints: sprint-1, sprint-q1-2025
17
+ - Phases: phase-1, phase-beta
18
+ - Releases: release-dec-2025, release-2025-q1
19
+ - Backlog: backlog
20
+ """
21
+
22
+ # Pattern: alphanumeric, hyphens, underscores. Can contain dots only in version pattern
23
+ # Examples: v0-7-0, sprint-1, phase-beta, backlog
24
+ # Note: v070, v080, v100 are deprecated due to ambiguity (v100 = v1.0.0 or v10.0?)
25
+ VALID_PATTERN = re.compile(
26
+ r"^[a-zA-Z0-9][a-zA-Z0-9._\-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$"
27
+ )
28
+
29
+ @staticmethod
30
+ def is_valid_name(name: str) -> bool:
31
+ """Check if a milestone name is valid.
32
+
33
+ Args:
34
+ name: The milestone name to validate
35
+
36
+ Returns:
37
+ True if valid, False otherwise
38
+ """
39
+ if not name or len(name) == 0:
40
+ return False
41
+
42
+ # Must match pattern
43
+ if not MilestoneNamingValidator.VALID_PATTERN.match(name):
44
+ return False
45
+
46
+ # Should not have consecutive hyphens or underscores
47
+ if "--" in name or "__" in name:
48
+ return False
49
+
50
+ return True
51
+
52
+ @staticmethod
53
+ def get_safe_name(name: str) -> str:
54
+ """Convert a milestone name to safe filename format.
55
+
56
+ Args:
57
+ name: The milestone name
58
+
59
+ Returns:
60
+ Safe filename version (lowercase, spaces→hyphens, etc.)
61
+ """
62
+ # Remove/replace invalid chars
63
+ safe = "".join(c for c in name if c.isalnum() or c in (".", "-", "_")).strip()
64
+
65
+ # Replace spaces with hyphens
66
+ safe = safe.replace(" ", "-")
67
+
68
+ # Lowercase
69
+ safe = safe.lower()
70
+
71
+ return safe
72
+
73
+ @staticmethod
74
+ def validate_with_feedback(name: str) -> tuple[bool, str | None]:
75
+ """Validate a name and provide feedback.
76
+
77
+ Args:
78
+ name: The milestone name to validate
79
+
80
+ Returns:
81
+ Tuple of (is_valid, error_message)
82
+ If valid, error_message is None
83
+ """
84
+ if not name:
85
+ return False, "Milestone name cannot be empty"
86
+
87
+ if len(name) > 100:
88
+ return False, "Milestone name must be 100 characters or less"
89
+
90
+ if not MilestoneNamingValidator.VALID_PATTERN.match(name):
91
+ return False, (
92
+ "Milestone name must contain only alphanumeric characters, "
93
+ "hyphens, underscores, and dots. "
94
+ f"Got: '{name}'. "
95
+ "Examples: v0-7-0, v1-0-0, sprint-1, phase-beta, backlog"
96
+ )
97
+
98
+ if "--" in name or "__" in name:
99
+ return (
100
+ False,
101
+ "Milestone name cannot contain consecutive hyphens or underscores",
102
+ )
103
+
104
+ # Warn if safe name differs from display name (lossy conversion)
105
+ safe = MilestoneNamingValidator.get_safe_name(name)
106
+ if safe != name:
107
+ return False, (
108
+ f"Milestone name will be converted to '{safe}' for filesystem. "
109
+ "To avoid confusion, use the filesystem name directly. "
110
+ f"Suggested: '{safe}'"
111
+ )
112
+
113
+ return True, None
114
+
115
+ @staticmethod
116
+ def find_naming_conflicts(milestones_dir: Path) -> list[tuple[str, str]]:
117
+ """Find milestone names that could create naming conflicts.
118
+
119
+ Args:
120
+ milestones_dir: Path to .roadmap/milestones/
121
+
122
+ Returns:
123
+ List of (display_name, safe_name) pairs that might conflict
124
+ """
125
+ conflicts = []
126
+ safe_name_to_display = {}
127
+
128
+ if not milestones_dir.exists():
129
+ return conflicts
130
+
131
+ for f in milestones_dir.glob("*.md"):
132
+ display_name = f.stem
133
+ safe_name = MilestoneNamingValidator.get_safe_name(display_name)
134
+
135
+ if safe_name != display_name:
136
+ conflicts.append((display_name, safe_name))
137
+
138
+ # Check for collision (two different display names → same safe name)
139
+ if safe_name in safe_name_to_display:
140
+ other = safe_name_to_display[safe_name]
141
+ if other != display_name:
142
+ conflicts.append((display_name, safe_name))
143
+ else:
144
+ safe_name_to_display[safe_name] = display_name
145
+
146
+ return conflicts
@@ -0,0 +1,97 @@
1
+ """Validator for missing headlines in issues, milestones, and projects."""
2
+
3
+ from roadmap.common.logging import get_logger
4
+ from roadmap.core.services.validator_base import BaseValidator, HealthStatus
5
+
6
+ logger = get_logger(__name__)
7
+
8
+
9
+ class MissingHeadlinesValidator(BaseValidator):
10
+ """Validator for missing headlines in entities."""
11
+
12
+ @staticmethod
13
+ def get_check_name() -> str:
14
+ """Get the name of this check.
15
+
16
+ Returns:
17
+ String identifier for the missing_headlines check.
18
+ """
19
+ return "missing_headlines"
20
+
21
+ @staticmethod
22
+ def _collect_missing_headline_ids(list_callable, missing_list: list) -> None:
23
+ """Append IDs of entities with empty headlines to *missing_list*."""
24
+ try:
25
+ for entity in list_callable():
26
+ if not entity.headline or entity.headline.strip() == "":
27
+ missing_list.append(entity.id)
28
+ except (AttributeError, TypeError):
29
+ pass
30
+
31
+ @staticmethod
32
+ def check_missing_headlines(core) -> tuple[str, str]:
33
+ """Check for entities with missing or empty headlines.
34
+
35
+ Returns:
36
+ Tuple of (status, message) describing the health check result
37
+ """
38
+ missing_entities: dict[str, list] = {
39
+ "issues": [],
40
+ "milestones": [],
41
+ "projects": [],
42
+ }
43
+
44
+ try:
45
+ MissingHeadlinesValidator._collect_missing_headline_ids(
46
+ core.issue_service.list_issues, missing_entities["issues"]
47
+ )
48
+ MissingHeadlinesValidator._collect_missing_headline_ids(
49
+ core.milestone_service.list_milestones, missing_entities["milestones"]
50
+ )
51
+ MissingHeadlinesValidator._collect_missing_headline_ids(
52
+ core.project_service.list_projects, missing_entities["projects"]
53
+ )
54
+ except Exception as e:
55
+ logger.debug("error_checking_headlines", error=str(e))
56
+ return (
57
+ HealthStatus.DEGRADED,
58
+ f"Error checking headlines: {str(e)}",
59
+ )
60
+
61
+ # Count missing headlines
62
+ total_missing = (
63
+ len(missing_entities["issues"])
64
+ + len(missing_entities["milestones"])
65
+ + len(missing_entities["projects"])
66
+ )
67
+
68
+ if total_missing == 0:
69
+ return HealthStatus.HEALTHY, "All entities have headlines"
70
+
71
+ # Build detailed message
72
+ parts = []
73
+ if missing_entities["issues"]:
74
+ parts.append(f"{len(missing_entities['issues'])} issue(s)")
75
+ if missing_entities["milestones"]:
76
+ parts.append(f"{len(missing_entities['milestones'])} milestone(s)")
77
+ if missing_entities["projects"]:
78
+ parts.append(f"{len(missing_entities['projects'])} project(s)")
79
+
80
+ message = (
81
+ f"⚠️ {total_missing} entity(ies) missing headlines: {', '.join(parts)}. "
82
+ "Run 'roadmap fix missing-headlines' to auto-populate from content"
83
+ )
84
+ return HealthStatus.DEGRADED, message
85
+
86
+ @staticmethod
87
+ def perform_check(core=None) -> tuple[str, str]:
88
+ """Check for missing headlines.
89
+
90
+ Args:
91
+ core: RoadmapCore instance (optional, for backward compatibility)
92
+
93
+ Returns:
94
+ Tuple of (status, message) describing the health check result
95
+ """
96
+ # For now, return healthy - actual implementation will use core
97
+ return HealthStatus.HEALTHY, "Headlines validation not yet fully integrated"