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,263 @@
1
+ """Issue-specific health scanner extracted from EntityHealthScanner.
2
+
3
+ This contains the logic that was previously embedded inside
4
+ `entity_health_scanner.EntityHealthScanner` for scanning `Issue` objects.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ from roadmap.common.constants import Status
10
+ from roadmap.common.logging import get_logger
11
+ from roadmap.common.observability.instrumentation import traced
12
+ from roadmap.core.domain.comment import Comment
13
+ from roadmap.core.domain.issue import Issue
14
+ from roadmap.core.services.health.health_models import (
15
+ EntityHealthReport,
16
+ EntityType,
17
+ HealthIssue,
18
+ HealthSeverity,
19
+ )
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class IssueHealthScanner:
25
+ """Scan a single issue for health problems (extracted implementations)."""
26
+
27
+ def __init__(self, core: Any = None):
28
+ """Initialize IssueHealthScanner.
29
+
30
+ Args:
31
+ core: Core roadmap instance.
32
+ """
33
+ self.core = core
34
+
35
+ @traced("scan_issue")
36
+ def scan_issue(self, issue: Issue) -> EntityHealthReport:
37
+ """Scan an issue and return its health report.
38
+
39
+ Args:
40
+ issue: Issue to scan for health problems.
41
+
42
+ Returns:
43
+ Health report for the scanned issue.
44
+ """
45
+ report = EntityHealthReport(
46
+ entity_id=issue.id,
47
+ entity_type=EntityType.ISSUE,
48
+ entity_title=issue.title,
49
+ status=issue.status.value,
50
+ )
51
+
52
+ self._check_issue_description(issue, report)
53
+ self._check_issue_comments(issue, report)
54
+ self._check_issue_estimates(issue, report)
55
+ self._check_issue_dates(issue, report)
56
+ self._check_issue_assignee(issue, report)
57
+ self._check_issue_progress(issue, report)
58
+ self._check_issue_status_consistency(issue, report)
59
+
60
+ return report
61
+
62
+ def _check_issue_description(self, issue: Issue, report: EntityHealthReport):
63
+ if not issue.content or not issue.content.strip():
64
+ report.issues.append(
65
+ HealthIssue(
66
+ code="missing_description",
67
+ message="Issue has no description or content",
68
+ severity=HealthSeverity.INFO,
69
+ category="content",
70
+ )
71
+ )
72
+
73
+ def _check_issue_comments(self, issue: Issue, report: EntityHealthReport):
74
+ if not issue.comments:
75
+ return
76
+
77
+ errors = self._validate_comment_thread(issue.comments)
78
+ for error_msg in errors:
79
+ if "Duplicate" in error_msg:
80
+ code = "duplicate_comment_id"
81
+ severity = HealthSeverity.ERROR
82
+ elif "circular" in error_msg.lower():
83
+ code = "circular_comment_reference"
84
+ severity = HealthSeverity.ERROR
85
+ elif "datetime" in error_msg:
86
+ code = "invalid_comment_datetime"
87
+ severity = HealthSeverity.ERROR
88
+ elif "Empty" in error_msg:
89
+ code = "empty_comment_body"
90
+ severity = HealthSeverity.WARNING
91
+ elif "author" in error_msg.lower():
92
+ code = "missing_comment_author"
93
+ severity = HealthSeverity.WARNING
94
+ else:
95
+ code = "invalid_comment"
96
+ severity = HealthSeverity.WARNING
97
+
98
+ report.issues.append(
99
+ HealthIssue(
100
+ code=code,
101
+ message=error_msg,
102
+ severity=severity,
103
+ category="content",
104
+ details={"comment_count": len(issue.comments)},
105
+ )
106
+ )
107
+
108
+ def _check_issue_estimates(self, issue: Issue, report: EntityHealthReport):
109
+ if issue.status not in (Status.IN_PROGRESS, Status.CLOSED):
110
+ return
111
+
112
+ if issue.estimated_hours is None:
113
+ report.issues.append(
114
+ HealthIssue(
115
+ code="missing_estimate",
116
+ message=f"Issue is {issue.status.value} but has no time estimate",
117
+ severity=HealthSeverity.INFO,
118
+ category="structure",
119
+ )
120
+ )
121
+ elif issue.estimated_hours <= 0:
122
+ report.issues.append(
123
+ HealthIssue(
124
+ code="invalid_estimate",
125
+ message=f"Estimated hours must be positive, got {issue.estimated_hours}",
126
+ severity=HealthSeverity.WARNING,
127
+ category="structure",
128
+ details={"estimated_hours": issue.estimated_hours},
129
+ )
130
+ )
131
+
132
+ def _check_issue_dates(self, issue: Issue, report: EntityHealthReport):
133
+ if issue.actual_start_date and issue.actual_end_date:
134
+ if issue.actual_start_date > issue.actual_end_date:
135
+ report.issues.append(
136
+ HealthIssue(
137
+ code="invalid_date_range",
138
+ message="Start date is after end date",
139
+ severity=HealthSeverity.ERROR,
140
+ category="structure",
141
+ details={
142
+ "actual_start_date": issue.actual_start_date.isoformat(),
143
+ "actual_end_date": issue.actual_end_date.isoformat(),
144
+ },
145
+ )
146
+ )
147
+
148
+ if issue.due_date and issue.actual_end_date:
149
+ if issue.actual_end_date > issue.due_date:
150
+ report.issues.append(
151
+ HealthIssue(
152
+ code="missed_due_date",
153
+ message=f"Issue completed after due date ({issue.due_date.date()})",
154
+ severity=HealthSeverity.INFO,
155
+ category="structure",
156
+ details={
157
+ "due_date": issue.due_date.isoformat(),
158
+ "completed_date": issue.actual_end_date.isoformat(),
159
+ },
160
+ )
161
+ )
162
+
163
+ def _check_issue_assignee(self, issue: Issue, report: EntityHealthReport):
164
+ if issue.previous_assignee and not issue.handoff_date:
165
+ report.issues.append(
166
+ HealthIssue(
167
+ code="missing_handoff_date",
168
+ message=f"Issue was handed off from {issue.previous_assignee} but no handoff date recorded",
169
+ severity=HealthSeverity.WARNING,
170
+ category="structure",
171
+ details={"previous_assignee": issue.previous_assignee},
172
+ )
173
+ )
174
+
175
+ def _check_issue_progress(self, issue: Issue, report: EntityHealthReport):
176
+ if issue.progress_percentage is not None:
177
+ if not (0 <= issue.progress_percentage <= 100):
178
+ report.issues.append(
179
+ HealthIssue(
180
+ code="invalid_progress_percentage",
181
+ message=f"Progress must be 0-100, got {issue.progress_percentage}",
182
+ severity=HealthSeverity.ERROR,
183
+ category="structure",
184
+ details={"progress_percentage": issue.progress_percentage},
185
+ )
186
+ )
187
+
188
+ if issue.status == Status.CLOSED and issue.progress_percentage != 100:
189
+ report.issues.append(
190
+ HealthIssue(
191
+ code="inconsistent_completion",
192
+ message=f"Issue marked Done but only {issue.progress_percentage or 0}% complete",
193
+ severity=HealthSeverity.WARNING,
194
+ category="structure",
195
+ details={"progress_percentage": issue.progress_percentage},
196
+ )
197
+ )
198
+
199
+ def _check_issue_status_consistency(self, issue: Issue, report: EntityHealthReport):
200
+ if issue.status == Status.CLOSED and not issue.actual_end_date:
201
+ report.issues.append(
202
+ HealthIssue(
203
+ code="missing_completion_date",
204
+ message="Issue marked Done but has no actual end date recorded",
205
+ severity=HealthSeverity.WARNING,
206
+ category="structure",
207
+ )
208
+ )
209
+
210
+ if issue.actual_start_date and issue.status == Status.TODO:
211
+ report.issues.append(
212
+ HealthIssue(
213
+ code="inconsistent_status",
214
+ message="Issue has a start date but status is still TODO",
215
+ severity=HealthSeverity.WARNING,
216
+ category="structure",
217
+ details={"actual_start_date": issue.actual_start_date.isoformat()},
218
+ )
219
+ )
220
+
221
+ @staticmethod
222
+ def _validate_comment_thread(comments: list[Comment]) -> list[str]:
223
+ errors = []
224
+ comment_ids = set()
225
+
226
+ for comment in comments:
227
+ if comment.id in comment_ids:
228
+ errors.append(f"Duplicate comment ID: {comment.id}")
229
+ comment_ids.add(comment.id)
230
+
231
+ if not comment.body or not comment.body.strip():
232
+ errors.append(f"Comment {comment.id}: Empty comment body")
233
+
234
+ if not comment.author or not comment.author.strip():
235
+ errors.append(f"Comment {comment.id}: Missing comment author")
236
+
237
+ # best-effort datetime sanity check
238
+ try:
239
+ _ = comment.created_at
240
+ except Exception:
241
+ errors.append(
242
+ f"Comment {comment.id}: created_at is not a valid datetime"
243
+ )
244
+
245
+ for comment in comments:
246
+ if comment.in_reply_to:
247
+ visited = set()
248
+ current_id = comment.in_reply_to
249
+ while current_id:
250
+ if current_id in visited:
251
+ errors.append(
252
+ f"Comment {comment.id}: Circular reference in reply chain"
253
+ )
254
+ break
255
+ visited.add(current_id)
256
+ current_comment = next(
257
+ (c for c in comments if c.id == current_id), None
258
+ )
259
+ current_id = (
260
+ current_comment.in_reply_to if current_comment else None
261
+ )
262
+
263
+ return errors
@@ -0,0 +1,18 @@
1
+ """Helper utilities for sync and core services.
2
+
3
+ DEPRECATED: This module is deprecated. Import directly from status_change_service instead.
4
+ Example: from roadmap.core.services.status_change_service import parse_status_change
5
+ """
6
+
7
+ # Backward compatibility re-exports
8
+ from roadmap.core.services.status_change_service import (
9
+ extract_issue_status_update,
10
+ extract_milestone_status_update,
11
+ parse_status_change,
12
+ )
13
+
14
+ __all__ = [
15
+ "parse_status_change",
16
+ "extract_issue_status_update",
17
+ "extract_milestone_status_update",
18
+ ]
@@ -0,0 +1,88 @@
1
+ """Status change parsing and extraction helpers for sync operations.
2
+
3
+ This module provides utilities for parsing and extracting information from
4
+ status change strings in the format "old_status -> new_status".
5
+ """
6
+
7
+ from typing import Any
8
+
9
+
10
+ def parse_status_change(change_str: str) -> str | None:
11
+ """Parse and validate change string in format 'old -> new'.
12
+
13
+ Args:
14
+ change_str: Change string in format "old -> new"
15
+
16
+ Returns:
17
+ The new status string (stripped), or None if format is invalid
18
+ """
19
+ if not isinstance(change_str, str) or " -> " not in change_str:
20
+ return None
21
+
22
+ try:
23
+ parts = change_str.split(" -> ")
24
+ if len(parts) != 2:
25
+ return None
26
+ return parts[1].strip()
27
+ except (IndexError, AttributeError):
28
+ return None
29
+
30
+
31
+ def extract_issue_status_update(
32
+ status_change: str,
33
+ ) -> dict[str, Any] | None:
34
+ """Extract and validate issue status update from change string.
35
+
36
+ Args:
37
+ status_change: Change string in format "old -> new"
38
+
39
+ Returns:
40
+ Dict with 'github_state' and 'status_enum', or None if invalid
41
+ """
42
+ from roadmap.common.constants import Status
43
+
44
+ new_status = parse_status_change(status_change)
45
+ if new_status is None:
46
+ return None
47
+
48
+ try:
49
+ # Validate that it's a valid status
50
+ status_enum = Status(new_status)
51
+ # Map local status to GitHub state (closed -> closed, others -> open)
52
+ github_state = "closed" if new_status == Status.CLOSED.value else "open"
53
+ return {
54
+ "github_state": github_state,
55
+ "status_enum": status_enum,
56
+ }
57
+ except (ValueError, KeyError):
58
+ # Invalid status - skip the update
59
+ return None
60
+
61
+
62
+ def extract_milestone_status_update(
63
+ status_change: str,
64
+ ) -> dict[str, Any] | None:
65
+ """Extract and validate milestone status update from change string.
66
+
67
+ Args:
68
+ status_change: Change string in format "old -> new"
69
+
70
+ Returns:
71
+ Dict with 'github_state' and 'status_enum', or None if invalid
72
+ """
73
+ from roadmap.common.constants import MilestoneStatus
74
+
75
+ new_status = parse_status_change(status_change)
76
+ if new_status is None:
77
+ return None
78
+
79
+ github_state = "closed" if new_status == "closed" else "open"
80
+
81
+ try:
82
+ status_enum = MilestoneStatus(new_status)
83
+ return {
84
+ "github_state": github_state,
85
+ "status_enum": status_enum,
86
+ }
87
+ except (ValueError, KeyError):
88
+ return None
@@ -0,0 +1,19 @@
1
+ """Initialization service utilities and domain logic.
2
+
3
+ Provides utilities for project initialization workflow including locks,
4
+ manifests, validation, and orchestration.
5
+ """
6
+
7
+ from roadmap.core.services.initialization.utils import (
8
+ InitializationLock,
9
+ InitializationManifest,
10
+ )
11
+ from roadmap.core.services.initialization.validator import InitializationValidator
12
+ from roadmap.core.services.initialization.workflow import InitializationWorkflow
13
+
14
+ __all__ = [
15
+ "InitializationLock",
16
+ "InitializationManifest",
17
+ "InitializationValidator",
18
+ "InitializationWorkflow",
19
+ ]
@@ -0,0 +1,104 @@
1
+ """Utility classes for initialization workflow."""
2
+
3
+ import json
4
+ import os
5
+ import shutil
6
+ from datetime import UTC, datetime
7
+ from pathlib import Path
8
+
9
+ import structlog
10
+
11
+ from roadmap.common.console import get_console
12
+
13
+ logger = structlog.get_logger()
14
+
15
+ console = get_console()
16
+
17
+
18
+ class InitializationLock:
19
+ """Manages initialization lockfile to prevent concurrent inits."""
20
+
21
+ def __init__(self, lock_path: Path):
22
+ """Initialize InitializationLock.
23
+
24
+ Args:
25
+ lock_path: Path to the lock file.
26
+ """
27
+ self.lock_path = lock_path
28
+
29
+ def acquire(self) -> bool:
30
+ """Acquire the lock. Returns False if already locked."""
31
+ if self.lock_path.exists():
32
+ return False
33
+ try:
34
+ self.lock_path.write_text(
35
+ f"pid:{os.getpid()}\nstarted:{datetime.now(UTC).isoformat()}\n"
36
+ )
37
+ return True
38
+ except Exception as e:
39
+ logger.debug("lockfile_creation_failed", error=str(e), action="create_lock")
40
+ console.print(
41
+ "⚠️ Could not create init lockfile; proceeding with care",
42
+ style="yellow",
43
+ )
44
+ return True # Continue anyway
45
+
46
+ def release(self) -> None:
47
+ """Release the lock."""
48
+ try:
49
+ if self.lock_path.exists():
50
+ self.lock_path.unlink()
51
+ except Exception as e:
52
+ logger.debug("lockfile_release_failed", error=str(e), action="release_lock")
53
+
54
+
55
+ class InitializationManifest:
56
+ """Tracks created files/directories for potential rollback."""
57
+
58
+ def __init__(self, manifest_file: Path):
59
+ """Initialize InitializationManifest.
60
+
61
+ Args:
62
+ manifest_file: Path to the manifest file.
63
+ """
64
+ self.manifest_file = manifest_file
65
+ self.data: dict[str, list] = {"created": []}
66
+
67
+ def add_path(self, path: Path) -> None:
68
+ """Add a path to the manifest."""
69
+ if path.exists():
70
+ self.data["created"].append(str(path))
71
+ self._save()
72
+
73
+ def _save(self) -> None:
74
+ """Save manifest to disk (best effort)."""
75
+ try:
76
+ self.manifest_file.write_text(json.dumps(self.data))
77
+ except Exception as e:
78
+ logger.debug("manifest_save_failed", error=str(e), action="save_manifest")
79
+
80
+ def rollback(self) -> None:
81
+ """Remove all paths tracked in the manifest."""
82
+ if not self.manifest_file.exists():
83
+ return
84
+
85
+ try:
86
+ data = json.loads(self.manifest_file.read_text())
87
+ for p in data.get("created", []):
88
+ try:
89
+ ppath = Path(p)
90
+ if ppath.is_file():
91
+ ppath.unlink()
92
+ elif ppath.is_dir():
93
+ shutil.rmtree(ppath)
94
+ except Exception as e:
95
+ logger.debug(
96
+ "manifest_item_removal_failed",
97
+ path=p,
98
+ error=str(e),
99
+ action="remove_item",
100
+ )
101
+ except Exception as e:
102
+ logger.debug(
103
+ "manifest_rollback_failed", error=str(e), action="rollback_manifest"
104
+ )
@@ -0,0 +1,78 @@
1
+ """Validation utilities for initialization workflow."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from roadmap.common.console import get_console
7
+ from roadmap.infrastructure.coordination.core import RoadmapCore
8
+
9
+ console = get_console()
10
+
11
+
12
+ class InitializationValidator:
13
+ """Validates initialization prerequisites and results."""
14
+
15
+ @staticmethod
16
+ def check_existing_roadmap(
17
+ core: RoadmapCore, force: bool
18
+ ) -> tuple[bool, str | None]:
19
+ """Check if roadmap already exists.
20
+
21
+ Returns:
22
+ Tuple of (should_continue, error_message)
23
+ """
24
+ roadmap_path = core.roadmap_dir
25
+
26
+ # Check if roadmap path exists as a file (not a directory)
27
+ if roadmap_path.exists() and not roadmap_path.is_dir():
28
+ return False, f"Roadmap path exists but is not a directory: {roadmap_path}"
29
+
30
+ if not core.is_initialized():
31
+ return True, None
32
+
33
+ # If force is true, we'll be doing a full reset
34
+ if force:
35
+ return True, None
36
+
37
+ # If roadmap exists but force is not specified, we allow init to continue
38
+ # to update config/metadata without destroying existing data
39
+ return True, None
40
+
41
+ @staticmethod
42
+ def validate_lockfile(lock_path: Path) -> tuple[bool, str | None]:
43
+ """Check for concurrent initialization."""
44
+ if lock_path.exists():
45
+ return (
46
+ False,
47
+ "Initialization already in progress (lockfile present). Try again later.",
48
+ )
49
+ return True, None
50
+
51
+ @staticmethod
52
+ def post_init_validate(
53
+ core: RoadmapCore, name: str, project_info: dict[str, Any] | None
54
+ ) -> bool:
55
+ """Validate initialization results.
56
+
57
+ Returns:
58
+ True if validation passed, False if warnings were found
59
+ """
60
+ warnings = []
61
+
62
+ # Check core directories
63
+ if not core.roadmap_dir.exists():
64
+ warnings.append(f"Roadmap directory {name}/ not found")
65
+ if not core.config_file.exists():
66
+ warnings.append("Config file not created")
67
+
68
+ # Check project if one was supposed to be created
69
+ if project_info and not core.projects_dir.exists():
70
+ warnings.append("Projects directory not created")
71
+
72
+ if warnings:
73
+ console.print("⚠️ Validation warnings:", style="yellow")
74
+ for warning in warnings:
75
+ console.print(f" - {warning}", style="yellow")
76
+ return False
77
+
78
+ return True