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,101 @@
1
+ """Git conflict detection and handling service."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING
6
+
7
+ from roadmap.common.logging import get_logger
8
+
9
+ if TYPE_CHECKING:
10
+ from .state_manager import StateManager
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class ConflictService:
16
+ """Service for detecting and managing git conflicts in .roadmap directory."""
17
+
18
+ def __init__(self, state_manager: "StateManager"):
19
+ """Initialize with reference to state manager.
20
+
21
+ Args:
22
+ state_manager: StateManager instance for accessing sync state
23
+ """
24
+ self.state_manager = state_manager
25
+
26
+ def check_git_conflicts(self, roadmap_dir: Path | None = None) -> list[str]:
27
+ """Check for git conflicts in .roadmap directory."""
28
+ if roadmap_dir is None:
29
+ roadmap_dir = Path.cwd() / ".roadmap"
30
+
31
+ conflict_files = []
32
+
33
+ try:
34
+ if not roadmap_dir.exists():
35
+ return conflict_files
36
+
37
+ # Check for conflict markers in .roadmap files
38
+ for pattern in ["**/*.md", "**/*.yaml", "**/*.yml"]:
39
+ for file_path in roadmap_dir.glob(pattern):
40
+ try:
41
+ with open(file_path, encoding="utf-8") as f:
42
+ content = f.read()
43
+
44
+ # Look for git conflict markers
45
+ conflict_markers = ["<<<<<<<", "=======", ">>>>>>>"]
46
+ if any(marker in content for marker in conflict_markers):
47
+ conflict_files.append(
48
+ str(file_path.relative_to(Path.cwd()))
49
+ )
50
+
51
+ except Exception as e:
52
+ logger.warning(
53
+ "failed_to_check_conflicts",
54
+ error=str(e),
55
+ file_path=str(file_path),
56
+ )
57
+
58
+ if conflict_files:
59
+ logger.warning(
60
+ "git_conflicts_detected",
61
+ conflict_count=len(conflict_files),
62
+ conflicts=conflict_files,
63
+ )
64
+ self.state_manager.set_sync_state("git_conflicts_detected", "true")
65
+ self.state_manager.set_sync_state(
66
+ "conflict_files", json.dumps(conflict_files)
67
+ )
68
+ else:
69
+ self.state_manager.set_sync_state("git_conflicts_detected", "false")
70
+ self.state_manager.set_sync_state("conflict_files", "[]")
71
+
72
+ return conflict_files
73
+
74
+ except Exception as e:
75
+ logger.error(
76
+ "Failed to check git conflicts", error=str(e), severity="data_error"
77
+ )
78
+ return conflict_files
79
+
80
+ def has_git_conflicts(self) -> bool:
81
+ """Check if there are unresolved git conflicts."""
82
+ try:
83
+ conflicts_detected = self.state_manager.get_sync_state(
84
+ "git_conflicts_detected"
85
+ )
86
+ return conflicts_detected == "true"
87
+ except Exception as e:
88
+ logger.debug("git_conflict_check_failed", error=str(e))
89
+ # If we can't check, assume no conflicts to avoid blocking operations
90
+ return False
91
+
92
+ def get_conflict_files(self) -> list[str]:
93
+ """Get list of files with git conflicts."""
94
+ try:
95
+ conflict_files_json = self.state_manager.get_sync_state("conflict_files")
96
+ if conflict_files_json:
97
+ return json.loads(conflict_files_json)
98
+ return []
99
+ except Exception as e:
100
+ logger.debug("conflict_files_retrieval_failed", error=str(e))
101
+ return []
@@ -0,0 +1,79 @@
1
+ """Connection management for database operations."""
2
+
3
+ import sqlite3
4
+ from contextlib import contextmanager
5
+
6
+ from roadmap.common.logging import get_logger
7
+
8
+ from ..database_manager import DatabaseManager
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class ConnectionManager:
14
+ """Manages database connections and transactions."""
15
+
16
+ def __init__(self, db_manager: DatabaseManager):
17
+ """Initialize connection manager.
18
+
19
+ Args:
20
+ db_manager: DatabaseManager instance handling connection pooling
21
+ """
22
+ self._db_manager = db_manager
23
+ logger.debug("connection_manager_initialized")
24
+
25
+ def get_connection(self) -> sqlite3.Connection:
26
+ """Get thread-local database connection.
27
+
28
+ Returns:
29
+ sqlite3.Connection: Active database connection
30
+ """
31
+ return self._db_manager._get_connection()
32
+
33
+ @contextmanager
34
+ def transaction(self):
35
+ """Context manager for database transactions.
36
+
37
+ Yields:
38
+ Connection context with automatic commit/rollback on exception
39
+ """
40
+ with self._db_manager.transaction() as conn:
41
+ yield conn
42
+
43
+ def close(self):
44
+ """Close all database connections."""
45
+ logger.debug("Closing database connections")
46
+ self._db_manager.close()
47
+
48
+ def vacuum(self):
49
+ """Optimize database by vacuuming."""
50
+ logger.debug("Running database vacuum")
51
+ self._db_manager.vacuum()
52
+
53
+ def is_initialized(self) -> bool:
54
+ """Check if database is properly initialized.
55
+
56
+ Returns:
57
+ bool: True if database exists and is initialized
58
+ """
59
+ result = self._db_manager.is_initialized()
60
+ logger.debug("Database initialization check", initialized=result)
61
+ return result
62
+
63
+ def database_exists(self) -> bool:
64
+ """Check if database file exists and has tables.
65
+
66
+ Returns:
67
+ bool: True if database exists with tables
68
+ """
69
+ result = self._db_manager.database_exists()
70
+ logger.debug("Database existence check", exists=result)
71
+ return result
72
+
73
+ def is_safe_for_writes(self) -> tuple[bool, str]:
74
+ """Check if database is safe for write operations.
75
+
76
+ Returns:
77
+ tuple[bool, str]: (safe, message) indicating if writes are safe
78
+ """
79
+ return self._db_manager.is_safe_for_writes()
@@ -0,0 +1,89 @@
1
+ """Issue storage operations."""
2
+
3
+ from typing import Any
4
+
5
+ from roadmap.common.errors import OperationType, safe_operation
6
+ from roadmap.common.logging import get_logger
7
+
8
+ from ..repositories import IssueRepository
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class IssueStorage:
14
+ """Handles issue CRUD operations."""
15
+
16
+ def __init__(self, issue_repo: IssueRepository):
17
+ """Initialize issue storage.
18
+
19
+ Args:
20
+ issue_repo: IssueRepository instance for data access
21
+ """
22
+ self._issue_repo = issue_repo
23
+ logger.debug("issue_storage_initialized")
24
+
25
+ @safe_operation(OperationType.CREATE, "Issue", include_traceback=True)
26
+ def create(self, issue_data: dict[str, Any]) -> str:
27
+ """Create a new issue.
28
+
29
+ Args:
30
+ issue_data: Issue data dictionary
31
+
32
+ Returns:
33
+ str: ID of created issue
34
+ """
35
+ logger.debug("Creating issue", keys=list(issue_data.keys()))
36
+ return self._issue_repo.create(issue_data)
37
+
38
+ def get(self, issue_id: str) -> dict[str, Any] | None:
39
+ """Get issue by ID.
40
+
41
+ Args:
42
+ issue_id: Issue identifier
43
+
44
+ Returns:
45
+ dict: Issue data or None if not found
46
+ """
47
+ logger.debug("Getting issue", issue_id=issue_id)
48
+ return self._issue_repo.get(issue_id)
49
+
50
+ @safe_operation(OperationType.UPDATE, "Issue", include_traceback=True)
51
+ def update(self, issue_id: str, updates: dict[str, Any]) -> bool:
52
+ """Update issue.
53
+
54
+ Args:
55
+ issue_id: Issue identifier
56
+ updates: Dictionary of fields to update
57
+
58
+ Returns:
59
+ bool: True if update successful
60
+ """
61
+ logger.debug("Updating issue", issue_id=issue_id, keys=list(updates.keys()))
62
+ return self._issue_repo.update(issue_id, updates)
63
+
64
+ @safe_operation(OperationType.DELETE, "Issue", include_traceback=True)
65
+ def delete(self, issue_id: str) -> bool:
66
+ """Delete issue.
67
+
68
+ Args:
69
+ issue_id: Issue identifier
70
+
71
+ Returns:
72
+ bool: True if deletion successful
73
+ """
74
+ logger.debug("Deleting issue", issue_id=issue_id)
75
+ return self._issue_repo.delete(issue_id)
76
+
77
+ @safe_operation(OperationType.UPDATE, "Issue")
78
+ def mark_archived(self, issue_id: str, archived: bool = True) -> bool:
79
+ """Mark an issue as archived or unarchived.
80
+
81
+ Args:
82
+ issue_id: Issue identifier
83
+ archived: Whether to archive (True) or unarchive (False)
84
+
85
+ Returns:
86
+ bool: True if operation successful
87
+ """
88
+ logger.debug("Marking issue archived", issue_id=issue_id, archived=archived)
89
+ return self._issue_repo.mark_archived(issue_id, archived)
@@ -0,0 +1,80 @@
1
+ """Milestone storage operations."""
2
+
3
+ from typing import Any
4
+
5
+ from roadmap.common.errors import OperationType, safe_operation
6
+ from roadmap.common.logging import get_logger
7
+
8
+ from ..repositories import MilestoneRepository
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class MilestoneStorage:
14
+ """Handles milestone CRUD operations."""
15
+
16
+ def __init__(self, milestone_repo: MilestoneRepository):
17
+ """Initialize milestone storage.
18
+
19
+ Args:
20
+ milestone_repo: MilestoneRepository instance for data access
21
+ """
22
+ self._milestone_repo = milestone_repo
23
+ logger.debug("milestone_storage_initialized")
24
+
25
+ @safe_operation(OperationType.CREATE, "Milestone", include_traceback=True)
26
+ def create(self, milestone_data: dict[str, Any]) -> str:
27
+ """Create a new milestone.
28
+
29
+ Args:
30
+ milestone_data: Milestone data dictionary
31
+
32
+ Returns:
33
+ str: ID of created milestone
34
+ """
35
+ logger.debug("Creating milestone", keys=list(milestone_data.keys()))
36
+ return self._milestone_repo.create(milestone_data)
37
+
38
+ def get(self, milestone_id: str) -> dict[str, Any] | None:
39
+ """Get milestone by ID.
40
+
41
+ Args:
42
+ milestone_id: Milestone identifier
43
+
44
+ Returns:
45
+ dict: Milestone data or None if not found
46
+ """
47
+ logger.debug("Getting milestone", milestone_id=milestone_id)
48
+ return self._milestone_repo.get(milestone_id)
49
+
50
+ @safe_operation(OperationType.UPDATE, "Milestone", include_traceback=True)
51
+ def update(self, milestone_id: str, updates: dict[str, Any]) -> bool:
52
+ """Update milestone.
53
+
54
+ Args:
55
+ milestone_id: Milestone identifier
56
+ updates: Dictionary of fields to update
57
+
58
+ Returns:
59
+ bool: True if update successful
60
+ """
61
+ logger.debug(
62
+ "Updating milestone", milestone_id=milestone_id, keys=list(updates.keys())
63
+ )
64
+ return self._milestone_repo.update(milestone_id, updates)
65
+
66
+ @safe_operation(OperationType.UPDATE, "Milestone")
67
+ def mark_archived(self, milestone_id: str, archived: bool = True) -> bool:
68
+ """Mark a milestone as archived or unarchived.
69
+
70
+ Args:
71
+ milestone_id: Milestone identifier
72
+ archived: Whether to archive (True) or unarchive (False)
73
+
74
+ Returns:
75
+ bool: True if operation successful
76
+ """
77
+ logger.debug(
78
+ "Marking milestone archived", milestone_id=milestone_id, archived=archived
79
+ )
80
+ return self._milestone_repo.mark_archived(milestone_id, archived)
@@ -0,0 +1,102 @@
1
+ """Project storage operations."""
2
+
3
+ from typing import Any
4
+
5
+ from roadmap.common.errors import OperationType, safe_operation
6
+ from roadmap.common.logging import get_logger
7
+
8
+ from ..repositories import ProjectRepository
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class ProjectStorage:
14
+ """Handles project CRUD operations."""
15
+
16
+ def __init__(self, project_repo: ProjectRepository):
17
+ """Initialize project storage.
18
+
19
+ Args:
20
+ project_repo: ProjectRepository instance for data access
21
+ """
22
+ self._project_repo = project_repo
23
+ logger.debug("project_storage_initialized")
24
+
25
+ @safe_operation(OperationType.CREATE, "Project", include_traceback=True)
26
+ def create(self, project_data: dict[str, Any]) -> str:
27
+ """Create a new project.
28
+
29
+ Args:
30
+ project_data: Project data dictionary
31
+
32
+ Returns:
33
+ str: ID of created project
34
+ """
35
+ logger.debug("Creating project", keys=list(project_data.keys()))
36
+ return self._project_repo.create(project_data)
37
+
38
+ def get(self, project_id: str) -> dict[str, Any] | None:
39
+ """Get project by ID.
40
+
41
+ Args:
42
+ project_id: Project identifier
43
+
44
+ Returns:
45
+ dict: Project data or None if not found
46
+ """
47
+ logger.debug("Getting project", project_id=project_id)
48
+ return self._project_repo.get(project_id)
49
+
50
+ def list_all(self) -> list[dict[str, Any]]:
51
+ """List all projects.
52
+
53
+ Returns:
54
+ list: List of project data dictionaries
55
+ """
56
+ logger.debug("Listing all projects")
57
+ return self._project_repo.list_all()
58
+
59
+ @safe_operation(OperationType.UPDATE, "Project", include_traceback=True)
60
+ def update(self, project_id: str, updates: dict[str, Any]) -> bool:
61
+ """Update project.
62
+
63
+ Args:
64
+ project_id: Project identifier
65
+ updates: Dictionary of fields to update
66
+
67
+ Returns:
68
+ bool: True if update successful
69
+ """
70
+ logger.debug(
71
+ "Updating project", project_id=project_id, keys=list(updates.keys())
72
+ )
73
+ return self._project_repo.update(project_id, updates)
74
+
75
+ @safe_operation(OperationType.DELETE, "Project", include_traceback=True)
76
+ def delete(self, project_id: str) -> bool:
77
+ """Delete project and all related data.
78
+
79
+ Args:
80
+ project_id: Project identifier
81
+
82
+ Returns:
83
+ bool: True if deletion successful
84
+ """
85
+ logger.debug("Deleting project", project_id=project_id)
86
+ return self._project_repo.delete(project_id)
87
+
88
+ @safe_operation(OperationType.UPDATE, "Project")
89
+ def mark_archived(self, project_id: str, archived: bool = True) -> bool:
90
+ """Mark a project as archived or unarchived.
91
+
92
+ Args:
93
+ project_id: Project identifier
94
+ archived: Whether to archive (True) or unarchive (False)
95
+
96
+ Returns:
97
+ bool: True if operation successful
98
+ """
99
+ logger.debug(
100
+ "Marking project archived", project_id=project_id, archived=archived
101
+ )
102
+ return self._project_repo.mark_archived(project_id, archived)
@@ -0,0 +1,227 @@
1
+ """Database query service for aggregations and complex queries."""
2
+
3
+ import hashlib
4
+ import json
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from roadmap.common.logging import get_logger
8
+
9
+ if TYPE_CHECKING:
10
+ from .state_manager import StateManager
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class QueryService:
16
+ """Service for executing complex database queries and aggregations."""
17
+
18
+ def __init__(self, state_manager: "StateManager"):
19
+ """Initialize with reference to state manager.
20
+
21
+ Args:
22
+ state_manager: StateManager instance for database access
23
+ """
24
+ self.state_manager = state_manager
25
+
26
+ def has_file_changes(self) -> bool:
27
+ """Check if .roadmap/ files have changes since last sync."""
28
+ try:
29
+ # Get roadmap directory from database path
30
+ roadmap_dir = self.state_manager.db_path.parent
31
+
32
+ # Check for Markdown files with YAML frontmatter in relevant directories
33
+ md_files = []
34
+ for subdir in ["issues", "milestones", "projects"]:
35
+ subdir_path = roadmap_dir / subdir
36
+ if subdir_path.exists():
37
+ md_files.extend(subdir_path.rglob("*.md"))
38
+
39
+ if not md_files:
40
+ return False
41
+
42
+ # Check each file against stored hash
43
+ with self.state_manager.transaction() as conn:
44
+ for file_path in md_files:
45
+ if not file_path.exists():
46
+ continue
47
+
48
+ # Calculate current hash
49
+ current_hash = hashlib.sha256(file_path.read_bytes()).hexdigest()
50
+
51
+ # Get stored hash
52
+ result = conn.execute(
53
+ "SELECT content_hash FROM file_sync_state WHERE file_path = ?",
54
+ (str(file_path.relative_to(roadmap_dir)),),
55
+ ).fetchone()
56
+
57
+ if not result or result[0] != current_hash:
58
+ # File is new or changed
59
+ return True
60
+
61
+ return False
62
+
63
+ except Exception as e:
64
+ logger.warning(
65
+ "Error checking file changes", error=str(e), severity="operational"
66
+ )
67
+ # If we can't check, assume changes exist to be safe
68
+ return True
69
+
70
+ def get_all_issues(self) -> list[dict[str, Any]]:
71
+ """Get all issues from database."""
72
+ try:
73
+ with self.state_manager.transaction() as conn:
74
+ results = conn.execute("""
75
+ SELECT i.id, i.title, i.status, i.priority, i.issue_type,
76
+ i.assignee, i.estimate_hours, i.due_date,
77
+ i.project_id, i.milestone_id, i.metadata,
78
+ m.title as milestone_name, p.name as project_name
79
+ FROM issues i
80
+ LEFT JOIN milestones m ON i.milestone_id = m.id
81
+ LEFT JOIN projects p ON i.project_id = p.id
82
+ ORDER BY i.title
83
+ """).fetchall()
84
+
85
+ issues = []
86
+ for row in results:
87
+ issue = {
88
+ "id": row[0],
89
+ "title": row[1],
90
+ "status": row[2],
91
+ "priority": row[3],
92
+ "type": row[4],
93
+ "assignee": row[5],
94
+ "estimate_hours": row[6],
95
+ "due_date": row[7],
96
+ "project_id": row[8],
97
+ "milestone_id": row[9],
98
+ "milestone_name": row[11],
99
+ "project_name": row[12],
100
+ }
101
+
102
+ # Parse metadata
103
+ if row[10]:
104
+ try:
105
+ metadata = json.loads(row[10])
106
+ issue.update(metadata)
107
+ except json.JSONDecodeError:
108
+ logger.warning(
109
+ "failed_to_parse_issue_metadata", severity="data_error"
110
+ )
111
+
112
+ issues.append(issue)
113
+
114
+ return issues
115
+
116
+ except Exception as e:
117
+ logger.error(
118
+ "Failed to get issues", error=str(e), severity="infrastructure"
119
+ )
120
+ return []
121
+
122
+ def get_all_milestones(self) -> list[dict[str, Any]]:
123
+ """Get all milestones from database."""
124
+ try:
125
+ with self.state_manager.transaction() as conn:
126
+ results = conn.execute("""
127
+ SELECT m.id, m.title, m.description, m.status, m.due_date,
128
+ m.progress_percentage, m.project_id, m.metadata,
129
+ p.name as project_name
130
+ FROM milestones m
131
+ LEFT JOIN projects p ON m.project_id = p.id
132
+ ORDER BY m.title
133
+ """).fetchall()
134
+
135
+ milestones = []
136
+ for row in results:
137
+ milestone = {
138
+ "id": row[0],
139
+ "name": row[1], # Use 'name' for compatibility
140
+ "title": row[1],
141
+ "description": row[2],
142
+ "status": row[3],
143
+ "due_date": row[4],
144
+ "progress_percentage": row[5],
145
+ "project_id": row[6],
146
+ "project_name": row[8],
147
+ }
148
+
149
+ # Parse metadata
150
+ if row[7]:
151
+ try:
152
+ metadata = json.loads(row[7])
153
+ milestone.update(metadata)
154
+ except json.JSONDecodeError:
155
+ logger.warning(
156
+ "failed_to_parse_milestone_metadata",
157
+ severity="data_error",
158
+ )
159
+
160
+ milestones.append(milestone)
161
+
162
+ return milestones
163
+
164
+ except Exception as e:
165
+ logger.error(
166
+ "Failed to get milestones", error=str(e), severity="infrastructure"
167
+ )
168
+ return []
169
+
170
+ def get_milestone_progress(self, milestone_name: str) -> dict[str, int]:
171
+ """Get progress stats for a milestone."""
172
+ try:
173
+ with self.state_manager.transaction() as conn:
174
+ # Get milestone ID first
175
+ milestone_result = conn.execute(
176
+ "SELECT id FROM milestones WHERE title = ?", (milestone_name,)
177
+ ).fetchone()
178
+
179
+ if not milestone_result:
180
+ return {"total": 0, "completed": 0}
181
+
182
+ milestone_id = milestone_result[0]
183
+
184
+ # Count total and done issues for this milestone
185
+ total_result = conn.execute(
186
+ "SELECT COUNT(*) FROM issues WHERE milestone_id = ?",
187
+ (milestone_id,),
188
+ ).fetchone()
189
+
190
+ completed_result = conn.execute(
191
+ "SELECT COUNT(*) FROM issues WHERE milestone_id = ? AND status = 'closed'",
192
+ (milestone_id,),
193
+ ).fetchone()
194
+
195
+ return {
196
+ "total": total_result[0] if total_result else 0,
197
+ "completed": completed_result[0] if completed_result else 0,
198
+ }
199
+
200
+ except Exception as e:
201
+ logger.error(
202
+ "failed_to_get_milestone_progress",
203
+ error=str(e),
204
+ milestone_name=milestone_name,
205
+ )
206
+ return {"total": 0, "completed": 0}
207
+
208
+ def get_issues_by_status(self) -> dict[str, int]:
209
+ """Get issue counts by status."""
210
+ try:
211
+ with self.state_manager.transaction() as conn:
212
+ results = conn.execute("""
213
+ SELECT status, COUNT(*)
214
+ FROM issues
215
+ GROUP BY status
216
+ ORDER BY status
217
+ """).fetchall()
218
+
219
+ return {row[0]: row[1] for row in results}
220
+
221
+ except Exception as e:
222
+ logger.error(
223
+ "Failed to get issues by status",
224
+ error=str(e),
225
+ severity="infrastructure",
226
+ )
227
+ return {}