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,97 @@
1
+ """Hook registry and detection for Git hooks."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from structlog import get_logger
7
+
8
+ logger = get_logger()
9
+
10
+
11
+ class HookRegistry:
12
+ """Registry and metadata for available Git hooks."""
13
+
14
+ AVAILABLE_HOOKS = ["post-commit", "pre-push", "post-merge", "post-checkout"]
15
+
16
+ @classmethod
17
+ def get_available_hooks(cls) -> list[str]:
18
+ """Get list of all available hook names."""
19
+ return cls.AVAILABLE_HOOKS.copy()
20
+
21
+ @classmethod
22
+ def is_valid_hook(cls, hook_name: str) -> bool:
23
+ """Check if hook name is valid."""
24
+ return hook_name in cls.AVAILABLE_HOOKS
25
+
26
+ @classmethod
27
+ def validate_hooks(cls, hooks: list[str] | None) -> list[str]:
28
+ """Validate and filter hook list.
29
+
30
+ Args:
31
+ hooks: List of hook names to validate
32
+
33
+ Returns:
34
+ Validated list of hook names
35
+ """
36
+ if hooks is None:
37
+ return cls.get_available_hooks()
38
+ return [h for h in hooks if cls.is_valid_hook(h)]
39
+
40
+
41
+ class HookStatus:
42
+ """Utility for checking hook installation status."""
43
+
44
+ @staticmethod
45
+ def get_hook_file(hook_name: str, hooks_dir: Path) -> Path:
46
+ """Get the file path for a hook."""
47
+ return hooks_dir / hook_name
48
+
49
+ @staticmethod
50
+ def is_hook_installed(hook_file: Path) -> bool:
51
+ """Check if hook file exists and is readable."""
52
+ if not hook_file.exists():
53
+ return False
54
+ try:
55
+ hook_file.read_text()
56
+ return True
57
+ except Exception as e:
58
+ logger.error(
59
+ "hook_install_check_failed", error=str(e), severity="operational"
60
+ )
61
+ return False
62
+
63
+ @staticmethod
64
+ def is_roadmap_hook(hook_file: Path) -> bool:
65
+ """Check if hook file is a roadmap hook."""
66
+ if not hook_file.exists():
67
+ return False
68
+ try:
69
+ content = hook_file.read_text()
70
+ return "roadmap-hook" in content
71
+ except Exception as e:
72
+ logger.error(
73
+ "hook_content_check_failed", error=str(e), severity="operational"
74
+ )
75
+ return False
76
+
77
+ @staticmethod
78
+ def is_executable(hook_file: Path) -> bool:
79
+ """Check if hook file is executable."""
80
+ if not hook_file.exists():
81
+ return False
82
+ try:
83
+ return bool(hook_file.stat().st_mode & 0o111)
84
+ except Exception as e:
85
+ logger.debug("hook_executable_check_failed", error=str(e))
86
+ return False
87
+
88
+ @staticmethod
89
+ def get_status_dict(hook_name: str, hook_file: Path) -> dict[str, Any]:
90
+ """Get complete status dictionary for a hook."""
91
+ return {
92
+ "installed": HookStatus.is_hook_installed(hook_file),
93
+ "is_roadmap_hook": HookStatus.is_roadmap_hook(hook_file),
94
+ "executable": HookStatus.is_executable(hook_file),
95
+ "file_exists": hook_file.exists(),
96
+ "file_path": str(hook_file) if hook_file.exists() else None,
97
+ }
@@ -0,0 +1,58 @@
1
+ """Hook script generation for Git hooks."""
2
+
3
+ import shutil
4
+
5
+
6
+ class HookContentGenerator:
7
+ """Generates content for Git hook bash scripts."""
8
+
9
+ BASH_TEMPLATE = """#!/bin/bash
10
+ # roadmap-hook: {hook_name}
11
+ # Auto-generated Git hook for roadmap integration
12
+
13
+ # Get the directory of this script
14
+ HOOK_DIR="$(cd "$(dirname "${{BASH_SOURCE[0]}}")" && pwd)"
15
+ REPO_ROOT="$(cd "$HOOK_DIR/../.." && pwd)"
16
+
17
+ # Change to repository root
18
+ cd "$REPO_ROOT"
19
+
20
+ # Execute roadmap hook handler
21
+ {python_exec} << 'PYTHON_HOOK_EOF'
22
+ import sys
23
+ sys.path.insert(0, '.')
24
+ try:
25
+ from roadmap.adapters.git.git_hooks_manager import GitHookManager
26
+ from roadmap.infrastructure.coordination.core import RoadmapCore
27
+ core = RoadmapCore()
28
+ hook_manager = GitHookManager(core)
29
+ getattr(hook_manager, "{handler_name}")()
30
+ except Exception as e:
31
+ # Silent fail to avoid breaking Git operations
32
+ pass
33
+ PYTHON_HOOK_EOF
34
+ """
35
+
36
+ @staticmethod
37
+ def get_python_executable() -> str:
38
+ """Get the path to Python executable."""
39
+ return shutil.which("python") or shutil.which("python3") or "python"
40
+
41
+ @classmethod
42
+ def generate(cls, hook_name: str) -> str:
43
+ """Generate hook script content for given hook name.
44
+
45
+ Args:
46
+ hook_name: Name of the hook (e.g., 'post-commit')
47
+
48
+ Returns:
49
+ Complete bash script content for the hook
50
+ """
51
+ handler_name = hook_name.replace("-", "_")
52
+ python_exec = cls.get_python_executable()
53
+
54
+ return cls.BASH_TEMPLATE.format(
55
+ hook_name=hook_name,
56
+ handler_name=handler_name,
57
+ python_exec=python_exec,
58
+ )
@@ -0,0 +1,409 @@
1
+ """GitSyncMonitor - Detect and sync file changes to database cache."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from roadmap.adapters.git.git_command_executor import GitCommandExecutor
7
+ from roadmap.common.logging import get_logger
8
+
9
+ logger = get_logger(__name__)
10
+
11
+
12
+ class GitSyncMonitor:
13
+ """Detects file changes via git diff and syncs them to database cache.
14
+
15
+ This class enables cheap change detection:
16
+ - Uses `git diff --name-only` for millisecond change detection
17
+ - Tracks last synced commit to avoid redundant work
18
+ - Filters changes to only .roadmap/issues/ files
19
+ - Syncs only changed files to database (vs filesystem scan)
20
+
21
+ Typical usage:
22
+ monitor = GitSyncMonitor(repo_path, state_manager)
23
+ changes = monitor.detect_changes()
24
+ if changes:
25
+ monitor.sync_to_database(changes)
26
+
27
+ This enables:
28
+ - `roadmap list`: git diff (50ms) + DB query (5ms) = 55ms total
29
+ - Instead of: filesystem scan (1000ms) + parse (500ms) = 1500ms total
30
+ """
31
+
32
+ def __init__(self, repo_path: Path | None = None, state_manager: Any | None = None):
33
+ """Initialize GitSyncMonitor.
34
+
35
+ Args:
36
+ repo_path: Path to git repository root
37
+ state_manager: StateManager for database operations (optional for Phase 1)
38
+ """
39
+ self.repo_path = repo_path or Path.cwd()
40
+ self.state_manager = state_manager
41
+ self.git_executor = GitCommandExecutor(self.repo_path)
42
+
43
+ # Cache last known commit to avoid re-checking
44
+ self._cached_last_synced_commit: str | None = None
45
+ self._cached_current_commit: str | None = None
46
+
47
+ def detect_changes(self) -> dict[str, str]:
48
+ """Detect changes since last sync.
49
+
50
+ Uses `git diff --name-only` for fast change detection.
51
+
52
+ Returns:
53
+ Dictionary mapping file paths to change types:
54
+ - "modified": File exists and changed
55
+ - "added": New file
56
+ - "deleted": File was removed
57
+ - Empty dict if no changes detected
58
+
59
+ Example:
60
+ {
61
+ ".roadmap/issues/issue-123.yaml": "modified",
62
+ ".roadmap/issues/issue-456.yaml": "added",
63
+ ".roadmap/issues/issue-789.yaml": "deleted",
64
+ }
65
+ """
66
+ try:
67
+ # Check if we're in a git repo
68
+ if not self.git_executor.is_git_repository():
69
+ logger.debug("Not in a git repository, skipping git sync")
70
+ return {}
71
+
72
+ # Get current commit
73
+ current_commit = self._get_current_commit()
74
+ if not current_commit:
75
+ logger.debug("Unable to get current commit")
76
+ return {}
77
+
78
+ # Get last synced commit
79
+ last_synced = self._get_last_synced_commit()
80
+ if last_synced == current_commit:
81
+ logger.debug(
82
+ "Already synced to current commit",
83
+ current=current_commit[:8],
84
+ )
85
+ return {}
86
+
87
+ # Get changed files
88
+ changes = self._get_changed_files(last_synced)
89
+ logger.debug(
90
+ "Detected file changes",
91
+ current=current_commit[:8],
92
+ previous=last_synced[:8] if last_synced else "none",
93
+ change_count=len(changes),
94
+ )
95
+
96
+ return changes
97
+
98
+ except Exception as e:
99
+ logger.error(
100
+ "Error detecting changes",
101
+ error=str(e),
102
+ error_type=type(e).__name__,
103
+ severity="system_error",
104
+ )
105
+ return {}
106
+
107
+ def sync_to_database(self, changes: dict[str, str]) -> bool:
108
+ """Sync detected changes to database cache.
109
+
110
+ This syncs remote_ids from changed YAML files to the database cache,
111
+ enabling fast O(1) lookups during sync operations instead of scanning
112
+ all YAML files.
113
+
114
+ Args:
115
+ changes: Dictionary of changes from detect_changes()
116
+
117
+ Returns:
118
+ True if sync successful, False otherwise
119
+ """
120
+ if not changes:
121
+ logger.debug("No changes to sync")
122
+ return True
123
+
124
+ if not self.state_manager:
125
+ logger.warning("State manager not configured, skipping database sync")
126
+ return False
127
+
128
+ try:
129
+ logger.debug("Syncing changes to database", change_count=len(changes))
130
+
131
+ # Sync remote_ids from changed files to database
132
+ from roadmap.adapters.persistence.parser import IssueParser
133
+
134
+ if self.state_manager.remote_links:
135
+ synced_count = 0
136
+ for file_path, change_type in changes.items():
137
+ try:
138
+ if change_type == "deleted":
139
+ # For deleted files, we can't read them, so skip
140
+ continue
141
+
142
+ # Load issue from file using IssueParser
143
+ file_path_obj = Path(file_path)
144
+ if not file_path_obj.is_absolute():
145
+ # Make path absolute relative to repo root
146
+ file_path_obj = self.repo_path / file_path_obj
147
+
148
+ if not file_path_obj.exists():
149
+ continue
150
+
151
+ issue = IssueParser.parse_issue_file(file_path_obj)
152
+ if issue and issue.remote_ids:
153
+ # Sync remote_ids to database
154
+ for backend_name, remote_id in issue.remote_ids.items():
155
+ if self.state_manager.remote_links.link_issue(
156
+ issue.id, backend_name, remote_id
157
+ ):
158
+ synced_count += 1
159
+ except Exception as e:
160
+ logger.warning(
161
+ "Failed to sync remote_ids for file",
162
+ file_path=file_path,
163
+ error=str(e),
164
+ )
165
+
166
+ if synced_count > 0:
167
+ logger.debug(
168
+ "Synced remote_ids to database",
169
+ synced_count=synced_count,
170
+ )
171
+
172
+ # Update last synced commit
173
+ self._save_last_synced_commit()
174
+
175
+ logger.debug("Sync to database complete")
176
+ return True
177
+
178
+ except Exception as e:
179
+ logger.error(
180
+ "Error syncing to database",
181
+ error=str(e),
182
+ error_type=type(e).__name__,
183
+ severity="system_error",
184
+ )
185
+ return False
186
+
187
+ def _get_changed_files(self, base_commit: str | None = None) -> dict[str, str]:
188
+ """Get list of changed files since base commit.
189
+
190
+ Args:
191
+ base_commit: Commit SHA to compare against. If None, uses initial commit.
192
+
193
+ Returns:
194
+ Dictionary mapping file paths to change types
195
+ """
196
+ changes: dict[str, str] = {}
197
+
198
+ try:
199
+ # If no base commit (first sync), get all issues files
200
+ if not base_commit:
201
+ return self._get_all_issues_files()
202
+
203
+ # Get changed files between base and current
204
+ changed_files = self.git_executor.run(
205
+ ["diff", "--name-status", base_commit, "HEAD"]
206
+ )
207
+
208
+ if not changed_files:
209
+ return {}
210
+
211
+ # Parse git diff output
212
+ # Format: M\tpath/to/file (Modified)
213
+ # A\tpath/to/file (Added)
214
+ # D\tpath/to/file (Deleted)
215
+ for line in changed_files.split("\n"):
216
+ if not line.strip():
217
+ continue
218
+
219
+ parts = line.split("\t", 1)
220
+ if len(parts) != 2:
221
+ continue
222
+
223
+ status, path = parts
224
+ path = path.strip()
225
+
226
+ # Only track .roadmap/issues/ changes
227
+ if not self._is_issues_file(path):
228
+ continue
229
+
230
+ # Map git status to our status
231
+ status_map = {"M": "modified", "A": "added", "D": "deleted"}
232
+ changes[path] = status_map.get(status, "modified")
233
+
234
+ return changes
235
+
236
+ except Exception as e:
237
+ logger.error(
238
+ "Error getting changed files",
239
+ error=str(e),
240
+ error_type=type(e).__name__,
241
+ severity="system_error",
242
+ )
243
+ return {}
244
+
245
+ def _get_all_issues_files(self) -> dict[str, str]:
246
+ """Get all issues files for initial sync.
247
+
248
+ Returns:
249
+ Dictionary mapping all issue files to "added" status
250
+ """
251
+ all_files: dict[str, str] = {}
252
+
253
+ try:
254
+ # Use git ls-files to get all tracked files
255
+ git_files = self.git_executor.run(["ls-files"])
256
+ if not git_files:
257
+ return {}
258
+
259
+ for path in git_files.split("\n"):
260
+ path = path.strip()
261
+ if self._is_issues_file(path):
262
+ all_files[path] = "added"
263
+
264
+ logger.debug(
265
+ "Found all issues files for initial sync",
266
+ file_count=len(all_files),
267
+ )
268
+
269
+ return all_files
270
+
271
+ except Exception as e:
272
+ logger.error(
273
+ "Error getting all issues files",
274
+ error=str(e),
275
+ error_type=type(e).__name__,
276
+ )
277
+ return {}
278
+
279
+ def _is_issues_file(self, path: str) -> bool:
280
+ """Check if path is a .roadmap/issues/ file.
281
+
282
+ Args:
283
+ path: File path to check
284
+
285
+ Returns:
286
+ True if file is in .roadmap/issues/ or .roadmap/archive/issues/
287
+ """
288
+ return ".roadmap/issues/" in path or ".roadmap/archive/issues/" in path
289
+
290
+ def _get_current_commit(self) -> str | None:
291
+ """Get the current HEAD commit SHA.
292
+
293
+ Returns:
294
+ Commit SHA, or None if not in a valid git state
295
+ """
296
+ if self._cached_current_commit:
297
+ return self._cached_current_commit
298
+
299
+ try:
300
+ commit = self.git_executor.run(["rev-parse", "HEAD"])
301
+ self._cached_current_commit = commit
302
+ return commit
303
+ except Exception as e:
304
+ logger.error(
305
+ "Error getting current commit",
306
+ error=str(e),
307
+ error_type=type(e).__name__,
308
+ severity="system_error",
309
+ )
310
+ return None
311
+
312
+ def _get_last_synced_commit(self) -> str | None:
313
+ """Get the last commit we synced to database.
314
+
315
+ Returns:
316
+ Commit SHA, or None if never synced
317
+ """
318
+ if self._cached_last_synced_commit is not None:
319
+ return self._cached_last_synced_commit
320
+
321
+ try:
322
+ # Read from database sync_metadata table
323
+ if self.state_manager:
324
+ conn = self.state_manager._get_connection()
325
+ cursor = conn.cursor()
326
+ cursor.execute(
327
+ "SELECT value FROM sync_metadata WHERE key = ?",
328
+ ("last_synced_commit",),
329
+ )
330
+ row = cursor.fetchone()
331
+ if row:
332
+ commit = row[0]
333
+ self._cached_last_synced_commit = commit
334
+ return commit
335
+
336
+ # No previous sync
337
+ self._cached_last_synced_commit = None
338
+ return None
339
+
340
+ except Exception as e:
341
+ logger.error(
342
+ "Error getting last synced commit",
343
+ error=str(e),
344
+ severity="system_error",
345
+ error_type=type(e).__name__,
346
+ )
347
+ return None
348
+
349
+ def _save_last_synced_commit(self) -> bool:
350
+ """Save current commit as the last synced commit.
351
+
352
+ Returns:
353
+ True if save successful
354
+ """
355
+ try:
356
+ current_commit = self._get_current_commit()
357
+ if not current_commit:
358
+ logger.error("Cannot save last synced commit: no current commit")
359
+ return False
360
+
361
+ # Write to database sync_metadata table
362
+ if not self.state_manager:
363
+ logger.warning("State manager not configured, cannot save sync commit")
364
+ return False
365
+
366
+ conn = self.state_manager._get_connection()
367
+ cursor = conn.cursor()
368
+
369
+ # Try to update first, then insert if not found
370
+ cursor.execute(
371
+ "UPDATE sync_metadata SET value = ?, updated_at = CURRENT_TIMESTAMP WHERE key = ?",
372
+ (current_commit, "last_synced_commit"),
373
+ )
374
+
375
+ # If no rows were updated, insert new row
376
+ if cursor.rowcount == 0:
377
+ cursor.execute(
378
+ "INSERT INTO sync_metadata (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)",
379
+ ("last_synced_commit", current_commit),
380
+ )
381
+
382
+ conn.commit()
383
+
384
+ # Update cache
385
+ self._cached_last_synced_commit = current_commit
386
+
387
+ logger.debug(
388
+ "Saved last synced commit",
389
+ commit=current_commit[:8],
390
+ )
391
+
392
+ return True
393
+
394
+ except Exception as e:
395
+ logger.error(
396
+ "Error saving last synced commit",
397
+ error=str(e),
398
+ error_type=type(e).__name__,
399
+ severity="system_error",
400
+ )
401
+ return False
402
+
403
+ def clear_cache(self) -> None:
404
+ """Clear internal commit caches.
405
+
406
+ Useful for testing or forcing a fresh check.
407
+ """
408
+ self._cached_last_synced_commit = None
409
+ self._cached_current_commit = None