roadmap-cli 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (521) hide show
  1. roadmap/__init__.py +27 -0
  2. roadmap/adapters/__init__.py +1 -0
  3. roadmap/adapters/base_paginated_adapter.py +117 -0
  4. roadmap/adapters/cli/__init__.py +249 -0
  5. roadmap/adapters/cli/analysis/__init__.py +5 -0
  6. roadmap/adapters/cli/analysis/commands.py +370 -0
  7. roadmap/adapters/cli/analysis/presenter.py +125 -0
  8. roadmap/adapters/cli/archive_operations.py +86 -0
  9. roadmap/adapters/cli/cli_command_helpers.py +109 -0
  10. roadmap/adapters/cli/cli_confirmations.py +74 -0
  11. roadmap/adapters/cli/cli_error_handlers.py +146 -0
  12. roadmap/adapters/cli/cli_validators.py +39 -0
  13. roadmap/adapters/cli/commands/sync_status.py +308 -0
  14. roadmap/adapters/cli/comment/__init__.py +5 -0
  15. roadmap/adapters/cli/comment/commands.py +106 -0
  16. roadmap/adapters/cli/config/__init__.py +5 -0
  17. roadmap/adapters/cli/config/commands.py +198 -0
  18. roadmap/adapters/cli/console_exports.py +10 -0
  19. roadmap/adapters/cli/core.py +26 -0
  20. roadmap/adapters/cli/crud/__init__.py +47 -0
  21. roadmap/adapters/cli/crud/base_archive.py +208 -0
  22. roadmap/adapters/cli/crud/base_create.py +154 -0
  23. roadmap/adapters/cli/crud/base_delete.py +155 -0
  24. roadmap/adapters/cli/crud/base_restore.py +283 -0
  25. roadmap/adapters/cli/crud/base_update.py +193 -0
  26. roadmap/adapters/cli/crud/crud_helpers.py +206 -0
  27. roadmap/adapters/cli/crud/crud_utils.py +152 -0
  28. roadmap/adapters/cli/crud/entity_builders.py +374 -0
  29. roadmap/adapters/cli/data/__init__.py +8 -0
  30. roadmap/adapters/cli/data/commands.py +149 -0
  31. roadmap/adapters/cli/decorators.py +208 -0
  32. roadmap/adapters/cli/dtos/__init__.py +155 -0
  33. roadmap/adapters/cli/exception_handler.py +105 -0
  34. roadmap/adapters/cli/git/__init__.py +8 -0
  35. roadmap/adapters/cli/git/commands.py +475 -0
  36. roadmap/adapters/cli/git/handlers/__init__.py +1 -0
  37. roadmap/adapters/cli/git/handlers/git_authentication_handler.py +124 -0
  38. roadmap/adapters/cli/git/handlers/git_branch_handler.py +238 -0
  39. roadmap/adapters/cli/git/handlers/git_connectivity_handler.py +122 -0
  40. roadmap/adapters/cli/git/handlers/git_hooks_handler.py +105 -0
  41. roadmap/adapters/cli/git/hooks_config.py +327 -0
  42. roadmap/adapters/cli/git/status_display.py +158 -0
  43. roadmap/adapters/cli/health/__init__.py +8 -0
  44. roadmap/adapters/cli/health/db_integrity.py +204 -0
  45. roadmap/adapters/cli/health/enhancer.py +227 -0
  46. roadmap/adapters/cli/health/fixer.py +295 -0
  47. roadmap/adapters/cli/health/fixers/__init__.py +10 -0
  48. roadmap/adapters/cli/health/fixers/corrupted_comments_fixer.py +231 -0
  49. roadmap/adapters/cli/health/fixers/data_integrity_fixer.py +137 -0
  50. roadmap/adapters/cli/health/fixers/duplicate_issues_fixer.py +138 -0
  51. roadmap/adapters/cli/health/fixers/folder_structure_fixer.py +114 -0
  52. roadmap/adapters/cli/health/fixers/label_normalization_fixer.py +162 -0
  53. roadmap/adapters/cli/health/fixers/milestone_name_normalization_fixer.py +181 -0
  54. roadmap/adapters/cli/health/fixers/milestone_naming_compliance_fixer.py +244 -0
  55. roadmap/adapters/cli/health/fixers/milestone_parse_error_fixer.py +162 -0
  56. roadmap/adapters/cli/health/fixers/milestone_validation_fixer.py +211 -0
  57. roadmap/adapters/cli/health/fixers/old_backups_fixer.py +135 -0
  58. roadmap/adapters/cli/health/fixers/orphaned_issues_fixer.py +252 -0
  59. roadmap/adapters/cli/health/formatter.py +250 -0
  60. roadmap/adapters/cli/health/formatters.py +427 -0
  61. roadmap/adapters/cli/health/scan.py +293 -0
  62. roadmap/adapters/cli/init/__init__.py +5 -0
  63. roadmap/adapters/cli/init/commands.py +587 -0
  64. roadmap/adapters/cli/init.py +9 -0
  65. roadmap/adapters/cli/issues/__init__.py +70 -0
  66. roadmap/adapters/cli/issues/archive.py +137 -0
  67. roadmap/adapters/cli/issues/archive_class.py +253 -0
  68. roadmap/adapters/cli/issues/block.py +39 -0
  69. roadmap/adapters/cli/issues/close.py +216 -0
  70. roadmap/adapters/cli/issues/comment.py +178 -0
  71. roadmap/adapters/cli/issues/create.py +162 -0
  72. roadmap/adapters/cli/issues/delete.py +31 -0
  73. roadmap/adapters/cli/issues/deps.py +161 -0
  74. roadmap/adapters/cli/issues/issue_status_helpers.py +111 -0
  75. roadmap/adapters/cli/issues/link.py +308 -0
  76. roadmap/adapters/cli/issues/list.py +402 -0
  77. roadmap/adapters/cli/issues/lookup.py +96 -0
  78. roadmap/adapters/cli/issues/progress.py +102 -0
  79. roadmap/adapters/cli/issues/restore.py +96 -0
  80. roadmap/adapters/cli/issues/restore_class.py +57 -0
  81. roadmap/adapters/cli/issues/start.py +165 -0
  82. roadmap/adapters/cli/issues/sync_status.py +374 -0
  83. roadmap/adapters/cli/issues/unblock.py +43 -0
  84. roadmap/adapters/cli/issues/unlink.py +104 -0
  85. roadmap/adapters/cli/issues/update.py +120 -0
  86. roadmap/adapters/cli/issues/view.py +36 -0
  87. roadmap/adapters/cli/layout.py +220 -0
  88. roadmap/adapters/cli/mappers.py +200 -0
  89. roadmap/adapters/cli/milestones/__init__.py +52 -0
  90. roadmap/adapters/cli/milestones/archive.py +125 -0
  91. roadmap/adapters/cli/milestones/archive_class.py +236 -0
  92. roadmap/adapters/cli/milestones/assign.py +62 -0
  93. roadmap/adapters/cli/milestones/close.py +177 -0
  94. roadmap/adapters/cli/milestones/create.py +131 -0
  95. roadmap/adapters/cli/milestones/delete.py +27 -0
  96. roadmap/adapters/cli/milestones/kanban.py +123 -0
  97. roadmap/adapters/cli/milestones/list.py +87 -0
  98. roadmap/adapters/cli/milestones/recalculate.py +79 -0
  99. roadmap/adapters/cli/milestones/restore.py +87 -0
  100. roadmap/adapters/cli/milestones/restore_class.py +99 -0
  101. roadmap/adapters/cli/milestones/update.py +61 -0
  102. roadmap/adapters/cli/milestones/view.py +142 -0
  103. roadmap/adapters/cli/output_manager.py +319 -0
  104. roadmap/adapters/cli/presentation/base_presenter.py +121 -0
  105. roadmap/adapters/cli/presentation/cleanup_presenter.py +235 -0
  106. roadmap/adapters/cli/presentation/core_initialization_presenter.py +277 -0
  107. roadmap/adapters/cli/presentation/crud_presenter.py +134 -0
  108. roadmap/adapters/cli/presentation/daily_summary_presenter.py +266 -0
  109. roadmap/adapters/cli/presentation/issue_presenter.py +258 -0
  110. roadmap/adapters/cli/presentation/milestone_list_presenter.py +135 -0
  111. roadmap/adapters/cli/presentation/milestone_presenter.py +297 -0
  112. roadmap/adapters/cli/presentation/project_presenter.py +233 -0
  113. roadmap/adapters/cli/presentation/project_status_presenter.py +186 -0
  114. roadmap/adapters/cli/presentation/table_builders.py +95 -0
  115. roadmap/adapters/cli/projects/__init__.py +42 -0
  116. roadmap/adapters/cli/projects/archive.py +127 -0
  117. roadmap/adapters/cli/projects/archive_class.py +96 -0
  118. roadmap/adapters/cli/projects/close.py +94 -0
  119. roadmap/adapters/cli/projects/create.py +50 -0
  120. roadmap/adapters/cli/projects/delete.py +27 -0
  121. roadmap/adapters/cli/projects/list.py +227 -0
  122. roadmap/adapters/cli/projects/restore.py +87 -0
  123. roadmap/adapters/cli/projects/restore_class.py +56 -0
  124. roadmap/adapters/cli/projects/update.py +57 -0
  125. roadmap/adapters/cli/projects/view.py +157 -0
  126. roadmap/adapters/cli/services/__init__.py +1 -0
  127. roadmap/adapters/cli/services/daily_summary_service.py +313 -0
  128. roadmap/adapters/cli/services/export_manager.py +268 -0
  129. roadmap/adapters/cli/services/milestone_list_service.py +266 -0
  130. roadmap/adapters/cli/services/project_initialization_service.py +29 -0
  131. roadmap/adapters/cli/services/project_status_service.py +462 -0
  132. roadmap/adapters/cli/services/sync_service.py +62 -0
  133. roadmap/adapters/cli/status.py +408 -0
  134. roadmap/adapters/cli/styling.py +21 -0
  135. roadmap/adapters/cli/sync.py +1117 -0
  136. roadmap/adapters/cli/sync_context.py +432 -0
  137. roadmap/adapters/cli/sync_handlers/__init__.py +77 -0
  138. roadmap/adapters/cli/sync_handlers/apply_ops.py +522 -0
  139. roadmap/adapters/cli/sync_handlers/baseline_ops.py +366 -0
  140. roadmap/adapters/cli/sync_handlers/conflict_ops.py +161 -0
  141. roadmap/adapters/cli/sync_handlers/dry_run_display.py +237 -0
  142. roadmap/adapters/cli/sync_handlers/interactive_resolver.py +396 -0
  143. roadmap/adapters/cli/sync_handlers/progress_tracker.py +128 -0
  144. roadmap/adapters/cli/sync_metrics_command.py +190 -0
  145. roadmap/adapters/cli/sync_presenter.py +58 -0
  146. roadmap/adapters/cli/sync_validation.py +295 -0
  147. roadmap/adapters/cli/today.py +52 -0
  148. roadmap/adapters/cli/utils/click_options.py +38 -0
  149. roadmap/adapters/git/__init__.py +1 -0
  150. roadmap/adapters/git/git.py +208 -0
  151. roadmap/adapters/git/git_branch_manager.py +400 -0
  152. roadmap/adapters/git/git_command_executor.py +56 -0
  153. roadmap/adapters/git/git_commit_analyzer.py +334 -0
  154. roadmap/adapters/git/git_hooks.py +22 -0
  155. roadmap/adapters/git/git_hooks_manager.py +455 -0
  156. roadmap/adapters/git/git_repository_info.py +65 -0
  157. roadmap/adapters/git/hook_installer.py +106 -0
  158. roadmap/adapters/git/hook_registry.py +97 -0
  159. roadmap/adapters/git/hook_script_generator.py +58 -0
  160. roadmap/adapters/git/sync_monitor.py +409 -0
  161. roadmap/adapters/git/workflow_automation.py +286 -0
  162. roadmap/adapters/github/__init__.py +1 -0
  163. roadmap/adapters/github/github.py +360 -0
  164. roadmap/adapters/github/handlers/__init__.py +17 -0
  165. roadmap/adapters/github/handlers/base.py +337 -0
  166. roadmap/adapters/github/handlers/collaborators.py +165 -0
  167. roadmap/adapters/github/handlers/comments.py +118 -0
  168. roadmap/adapters/github/handlers/issues.py +150 -0
  169. roadmap/adapters/github/handlers/labels.py +164 -0
  170. roadmap/adapters/github/handlers/milestones.py +104 -0
  171. roadmap/adapters/persistence/__init__.py +35 -0
  172. roadmap/adapters/persistence/conflict_resolver.py +190 -0
  173. roadmap/adapters/persistence/database_manager.py +492 -0
  174. roadmap/adapters/persistence/entity_sync_coordinators.py +672 -0
  175. roadmap/adapters/persistence/file_locking.py +348 -0
  176. roadmap/adapters/persistence/file_parser.py +92 -0
  177. roadmap/adapters/persistence/file_synchronizer.py +107 -0
  178. roadmap/adapters/persistence/focused_managers.py +140 -0
  179. roadmap/adapters/persistence/git_history.py +330 -0
  180. roadmap/adapters/persistence/parser/__init__.py +20 -0
  181. roadmap/adapters/persistence/parser/frontmatter.py +152 -0
  182. roadmap/adapters/persistence/parser/issue.py +245 -0
  183. roadmap/adapters/persistence/parser/milestone.py +146 -0
  184. roadmap/adapters/persistence/parser/project.py +106 -0
  185. roadmap/adapters/persistence/persistence.py +288 -0
  186. roadmap/adapters/persistence/repositories/__init__.py +15 -0
  187. roadmap/adapters/persistence/repositories/issue_repository.py +223 -0
  188. roadmap/adapters/persistence/repositories/milestone_repository.py +140 -0
  189. roadmap/adapters/persistence/repositories/project_repository.py +168 -0
  190. roadmap/adapters/persistence/repositories/remote_link_repository.py +355 -0
  191. roadmap/adapters/persistence/repositories/sync_state_repository.py +66 -0
  192. roadmap/adapters/persistence/storage/__init__.py +36 -0
  193. roadmap/adapters/persistence/storage/conflicts.py +101 -0
  194. roadmap/adapters/persistence/storage/connection_manager.py +79 -0
  195. roadmap/adapters/persistence/storage/issue_storage.py +89 -0
  196. roadmap/adapters/persistence/storage/milestone_storage.py +80 -0
  197. roadmap/adapters/persistence/storage/project_storage.py +102 -0
  198. roadmap/adapters/persistence/storage/queries.py +227 -0
  199. roadmap/adapters/persistence/storage/state_manager.py +652 -0
  200. roadmap/adapters/persistence/storage/sync_state_storage.py +207 -0
  201. roadmap/adapters/persistence/sync_metrics_repository.py +342 -0
  202. roadmap/adapters/persistence/sync_orchestrator.py +293 -0
  203. roadmap/adapters/persistence/sync_state_tracker.py +86 -0
  204. roadmap/adapters/persistence/yaml_repositories.py +711 -0
  205. roadmap/adapters/sync/__init__.py +21 -0
  206. roadmap/adapters/sync/backend_factory.py +108 -0
  207. roadmap/adapters/sync/backends/__init__.py +15 -0
  208. roadmap/adapters/sync/backends/converters.py +209 -0
  209. roadmap/adapters/sync/backends/github_backend_helpers.py +380 -0
  210. roadmap/adapters/sync/backends/github_client.py +99 -0
  211. roadmap/adapters/sync/backends/github_sync_backend.py +792 -0
  212. roadmap/adapters/sync/backends/github_sync_ops.py +908 -0
  213. roadmap/adapters/sync/backends/services/__init__.py +1 -0
  214. roadmap/adapters/sync/backends/services/github_authentication_service.py +105 -0
  215. roadmap/adapters/sync/backends/services/github_issue_delete_service.py +423 -0
  216. roadmap/adapters/sync/backends/services/github_issue_dependency_service.py +63 -0
  217. roadmap/adapters/sync/backends/services/github_issue_fetch_service.py +158 -0
  218. roadmap/adapters/sync/backends/services/github_label_sync_service.py +126 -0
  219. roadmap/adapters/sync/backends/services/github_local_issue_sync_service.py +229 -0
  220. roadmap/adapters/sync/backends/services/github_milestone_fetch_service.py +204 -0
  221. roadmap/adapters/sync/backends/vanilla_git_sync_backend.py +198 -0
  222. roadmap/adapters/sync/services/__init__.py +11 -0
  223. roadmap/adapters/sync/services/baseline_state_handler.py +83 -0
  224. roadmap/adapters/sync/services/conflict_converter.py +85 -0
  225. roadmap/adapters/sync/services/issue_persistence_service.py +263 -0
  226. roadmap/adapters/sync/services/issue_state_service.py +243 -0
  227. roadmap/adapters/sync/services/local_change_filter.py +129 -0
  228. roadmap/adapters/sync/services/pull_result_processor.py +71 -0
  229. roadmap/adapters/sync/services/remote_issue_creation_service.py +112 -0
  230. roadmap/adapters/sync/services/sync_analysis_service.py +250 -0
  231. roadmap/adapters/sync/services/sync_authentication_service.py +57 -0
  232. roadmap/adapters/sync/services/sync_data_fetch_service.py +212 -0
  233. roadmap/adapters/sync/services/sync_linking_service.py +329 -0
  234. roadmap/adapters/sync/services/sync_report_service.py +62 -0
  235. roadmap/adapters/sync/services/sync_state_update_service.py +51 -0
  236. roadmap/adapters/sync/sync_cache_orchestrator.py +454 -0
  237. roadmap/adapters/sync/sync_merge_engine.py +416 -0
  238. roadmap/adapters/sync/sync_merge_orchestrator.py +1313 -0
  239. roadmap/adapters/sync/sync_retrieval_orchestrator.py +810 -0
  240. roadmap/application/services/__init__.py +1 -0
  241. roadmap/application/services/deduplicate_service.py +517 -0
  242. roadmap/application/use_cases/__init__.py +1 -0
  243. roadmap/common/__init__.py +308 -0
  244. roadmap/common/cache.py +209 -0
  245. roadmap/common/cli_errors.py +147 -0
  246. roadmap/common/configuration/__init__.py +33 -0
  247. roadmap/common/configuration/config_loader.py +264 -0
  248. roadmap/common/configuration/config_manager.py +164 -0
  249. roadmap/common/configuration/config_schema.py +145 -0
  250. roadmap/common/configuration/github/__init__.py +9 -0
  251. roadmap/common/configuration/github/config_manager.py +154 -0
  252. roadmap/common/configuration/github/token_resolver.py +84 -0
  253. roadmap/common/console.py +184 -0
  254. roadmap/common/constants.py +134 -0
  255. roadmap/common/datetime_parser.py +319 -0
  256. roadmap/common/error_formatter.py +84 -0
  257. roadmap/common/errors/__init__.py +143 -0
  258. roadmap/common/errors/error_base.py +73 -0
  259. roadmap/common/errors/error_file.py +82 -0
  260. roadmap/common/errors/error_git.py +67 -0
  261. roadmap/common/errors/error_handler.py +166 -0
  262. roadmap/common/errors/error_network.py +90 -0
  263. roadmap/common/errors/error_security.py +78 -0
  264. roadmap/common/errors/error_standards.py +561 -0
  265. roadmap/common/errors/error_validation.py +111 -0
  266. roadmap/common/errors/exceptions.py +432 -0
  267. roadmap/common/formatters/__init__.py +29 -0
  268. roadmap/common/formatters/base_table_formatter.py +96 -0
  269. roadmap/common/formatters/export/__init__.py +7 -0
  270. roadmap/common/formatters/export/issue_exporter.py +97 -0
  271. roadmap/common/formatters/helpers.py +28 -0
  272. roadmap/common/formatters/kanban/__init__.py +9 -0
  273. roadmap/common/formatters/kanban/layout.py +48 -0
  274. roadmap/common/formatters/kanban/organizer.py +81 -0
  275. roadmap/common/formatters/output/__init__.py +15 -0
  276. roadmap/common/formatters/tables/__init__.py +11 -0
  277. roadmap/common/formatters/tables/column_factory.py +312 -0
  278. roadmap/common/formatters/tables/issue_table.py +276 -0
  279. roadmap/common/formatters/tables/milestone_table.py +247 -0
  280. roadmap/common/formatters/tables/project_table.py +195 -0
  281. roadmap/common/formatters/text/__init__.py +57 -0
  282. roadmap/common/formatters/text/basic.py +217 -0
  283. roadmap/common/formatters/text/display.py +42 -0
  284. roadmap/common/formatters/text/duration.py +36 -0
  285. roadmap/common/formatters/text/operations.py +325 -0
  286. roadmap/common/formatters/text/status_badges.py +57 -0
  287. roadmap/common/initialization/github/__init__.py +11 -0
  288. roadmap/common/initialization/github/setup_service.py +238 -0
  289. roadmap/common/initialization/github/setup_validator.py +78 -0
  290. roadmap/common/logging/__init__.py +113 -0
  291. roadmap/common/logging/audit_logging.py +5 -0
  292. roadmap/common/logging/correlation.py +45 -0
  293. roadmap/common/logging/decorators.py +267 -0
  294. roadmap/common/logging/error_logging.py +217 -0
  295. roadmap/common/logging/formatters.py +124 -0
  296. roadmap/common/logging/loggers.py +68 -0
  297. roadmap/common/logging/performance_tracking.py +267 -0
  298. roadmap/common/logging/utils.py +335 -0
  299. roadmap/common/models/__init__.py +44 -0
  300. roadmap/common/models/cli_models.py +118 -0
  301. roadmap/common/models/config_models.py +199 -0
  302. roadmap/common/models/output_models.py +381 -0
  303. roadmap/common/observability/__init__.py +28 -0
  304. roadmap/common/observability/instrumentation.py +103 -0
  305. roadmap/common/observability/observability.py +76 -0
  306. roadmap/common/observability/otel_init.py +109 -0
  307. roadmap/common/output_formatter.py +393 -0
  308. roadmap/common/progress.py +379 -0
  309. roadmap/common/result.py +396 -0
  310. roadmap/common/security/__init__.py +48 -0
  311. roadmap/common/security/exceptions.py +13 -0
  312. roadmap/common/security/export_cleanup.py +79 -0
  313. roadmap/common/security/file_operations.py +104 -0
  314. roadmap/common/security/filename_sanitization.py +54 -0
  315. roadmap/common/security/logging.py +27 -0
  316. roadmap/common/security/path_validation.py +137 -0
  317. roadmap/common/security/temp_files.py +41 -0
  318. roadmap/common/services/__init__.py +65 -0
  319. roadmap/common/services/decorators.py +183 -0
  320. roadmap/common/services/logging_utils.py +208 -0
  321. roadmap/common/services/metrics.py +161 -0
  322. roadmap/common/services/performance.py +219 -0
  323. roadmap/common/services/profiling.py +288 -0
  324. roadmap/common/services/retry.py +288 -0
  325. roadmap/common/status_style_manager.py +114 -0
  326. roadmap/common/union_find.py +88 -0
  327. roadmap/common/update_constants.py +13 -0
  328. roadmap/common/utils/__init__.py +65 -0
  329. roadmap/common/utils/cli_helpers.py +381 -0
  330. roadmap/common/utils/file_utils.py +440 -0
  331. roadmap/common/utils/path_utils.py +38 -0
  332. roadmap/common/utils/status_utils.py +130 -0
  333. roadmap/common/utils/timezone_utils.py +480 -0
  334. roadmap/common/validation/__init__.py +37 -0
  335. roadmap/common/validation/field_validator.py +104 -0
  336. roadmap/common/validation/result.py +37 -0
  337. roadmap/common/validation/roadmap_validator.py +310 -0
  338. roadmap/common/validation/schema_validator.py +36 -0
  339. roadmap/common/validation/validators.py +50 -0
  340. roadmap/core/__init__.py +1 -0
  341. roadmap/core/domain/__init__.py +30 -0
  342. roadmap/core/domain/comment.py +24 -0
  343. roadmap/core/domain/health.py +15 -0
  344. roadmap/core/domain/issue.py +274 -0
  345. roadmap/core/domain/milestone.py +173 -0
  346. roadmap/core/domain/ports/__init__.py +1 -0
  347. roadmap/core/domain/ports/baseline_repository.py +24 -0
  348. roadmap/core/domain/ports/issue_repository.py +34 -0
  349. roadmap/core/domain/ports/remote_backend_port.py +31 -0
  350. roadmap/core/domain/project.py +130 -0
  351. roadmap/core/interfaces/__init__.py +109 -0
  352. roadmap/core/interfaces/assignee_validator.py +40 -0
  353. roadmap/core/interfaces/backend_factory.py +175 -0
  354. roadmap/core/interfaces/github.py +102 -0
  355. roadmap/core/interfaces/parsers.py +140 -0
  356. roadmap/core/interfaces/persistence.py +45 -0
  357. roadmap/core/interfaces/repositories.py +174 -0
  358. roadmap/core/interfaces/state_managers.py +111 -0
  359. roadmap/core/interfaces/state_storage.py +64 -0
  360. roadmap/core/interfaces/sync_backend.py +275 -0
  361. roadmap/core/interfaces/sync_services.py +119 -0
  362. roadmap/core/interfaces/sync_validators.py +180 -0
  363. roadmap/core/models/__init__.py +31 -0
  364. roadmap/core/models/sync_models.py +162 -0
  365. roadmap/core/models.py +115 -0
  366. roadmap/core/observability/__init__.py +17 -0
  367. roadmap/core/observability/sync_metrics.py +699 -0
  368. roadmap/core/repositories.py +200 -0
  369. roadmap/core/services/__init__.py +313 -0
  370. roadmap/core/services/baseline/__init__.py +1 -0
  371. roadmap/core/services/baseline/baseline_builder_progress.py +274 -0
  372. roadmap/core/services/baseline/baseline_retriever.py +63 -0
  373. roadmap/core/services/baseline/baseline_selector.py +248 -0
  374. roadmap/core/services/baseline/baseline_state_retriever.py +284 -0
  375. roadmap/core/services/baseline/optimized_baseline_builder.py +425 -0
  376. roadmap/core/services/comment/__init__.py +1 -0
  377. roadmap/core/services/comment/comment_service.py +277 -0
  378. roadmap/core/services/git/__init__.py +1 -0
  379. roadmap/core/services/git/git_hook_auto_sync_service.py +403 -0
  380. roadmap/core/services/github/__init__.py +1 -0
  381. roadmap/core/services/github/github_change_detector.py +162 -0
  382. roadmap/core/services/github/github_config_validator.py +148 -0
  383. roadmap/core/services/github/github_conflict_detector.py +179 -0
  384. roadmap/core/services/github/github_entity_classifier.py +51 -0
  385. roadmap/core/services/github/github_integration_service.py +439 -0
  386. roadmap/core/services/github/github_issue_client.py +346 -0
  387. roadmap/core/services/health/__init__.py +1 -0
  388. roadmap/core/services/health/backup_cleanup_service.py +202 -0
  389. roadmap/core/services/health/data_integrity_validator_service.py +90 -0
  390. roadmap/core/services/health/entity_health_scanner.py +460 -0
  391. roadmap/core/services/health/file_repair_service.py +193 -0
  392. roadmap/core/services/health/health_check_service.py +166 -0
  393. roadmap/core/services/health/health_models.py +80 -0
  394. roadmap/core/services/health/infrastructure_validator_service.py +328 -0
  395. roadmap/core/services/health/issue_health_scanner.py +263 -0
  396. roadmap/core/services/helpers/__init__.py +18 -0
  397. roadmap/core/services/helpers/status_change_helpers.py +88 -0
  398. roadmap/core/services/initialization/__init__.py +19 -0
  399. roadmap/core/services/initialization/utils.py +104 -0
  400. roadmap/core/services/initialization/validator.py +78 -0
  401. roadmap/core/services/initialization/workflow.py +182 -0
  402. roadmap/core/services/initialization_service.py +111 -0
  403. roadmap/core/services/issue/__init__.py +1 -0
  404. roadmap/core/services/issue/assignee_validation_service.py +312 -0
  405. roadmap/core/services/issue/issue_creation_service.py +309 -0
  406. roadmap/core/services/issue/issue_filter_service.py +302 -0
  407. roadmap/core/services/issue/issue_matching_service.py +127 -0
  408. roadmap/core/services/issue/issue_service.py +765 -0
  409. roadmap/core/services/issue/issue_update_service.py +144 -0
  410. roadmap/core/services/issue/start_issue_service.py +174 -0
  411. roadmap/core/services/issue_helpers/__init__.py +18 -0
  412. roadmap/core/services/issue_helpers/issue_filters.py +296 -0
  413. roadmap/core/services/milestone_service.py +438 -0
  414. roadmap/core/services/project/__init__.py +1 -0
  415. roadmap/core/services/project/project_service.py +434 -0
  416. roadmap/core/services/project/project_status_service.py +125 -0
  417. roadmap/core/services/project_init/__init__.py +22 -0
  418. roadmap/core/services/project_init/context_detection.py +146 -0
  419. roadmap/core/services/project_init/creation.py +76 -0
  420. roadmap/core/services/project_init/detection.py +51 -0
  421. roadmap/core/services/project_init/template.py +228 -0
  422. roadmap/core/services/status_change_service.py +88 -0
  423. roadmap/core/services/sync/__init__.py +1 -0
  424. roadmap/core/services/sync/batch_processor.py +290 -0
  425. roadmap/core/services/sync/conflict_resolver.py +154 -0
  426. roadmap/core/services/sync/dependency_resolver.py +367 -0
  427. roadmap/core/services/sync/duplicate_detector.py +771 -0
  428. roadmap/core/services/sync/duplicate_resolver.py +315 -0
  429. roadmap/core/services/sync/error_classification.py +470 -0
  430. roadmap/core/services/sync/performance.py +263 -0
  431. roadmap/core/services/sync/sync_change_computer.py +211 -0
  432. roadmap/core/services/sync/sync_checkpoint.py +348 -0
  433. roadmap/core/services/sync/sync_conflict_detector.py +147 -0
  434. roadmap/core/services/sync/sync_conflict_resolver.py +448 -0
  435. roadmap/core/services/sync/sync_errors.py +383 -0
  436. roadmap/core/services/sync/sync_key_normalizer.py +222 -0
  437. roadmap/core/services/sync/sync_metadata_service.py +361 -0
  438. roadmap/core/services/sync/sync_plan.py +171 -0
  439. roadmap/core/services/sync/sync_plan_executor.py +447 -0
  440. roadmap/core/services/sync/sync_report.py +296 -0
  441. roadmap/core/services/sync/sync_state.py +173 -0
  442. roadmap/core/services/sync/sync_state_comparator.py +690 -0
  443. roadmap/core/services/sync/sync_state_manager.py +431 -0
  444. roadmap/core/services/sync/sync_state_normalizer.py +98 -0
  445. roadmap/core/services/sync/sync_three_way.py +78 -0
  446. roadmap/core/services/sync/three_way_merger.py +178 -0
  447. roadmap/core/services/utils/__init__.py +1 -0
  448. roadmap/core/services/utils/configuration_service.py +113 -0
  449. roadmap/core/services/utils/critical_path_calculator.py +418 -0
  450. roadmap/core/services/utils/dependency_analyzer.py +360 -0
  451. roadmap/core/services/utils/field_conflict_detector.py +126 -0
  452. roadmap/core/services/utils/remote_fetcher.py +202 -0
  453. roadmap/core/services/utils/remote_state_normalizer.py +110 -0
  454. roadmap/core/services/utils/retry_policy.py +51 -0
  455. roadmap/core/services/validator_base.py +130 -0
  456. roadmap/core/services/validators/__init__.py +28 -0
  457. roadmap/core/services/validators/_utils.py +20 -0
  458. roadmap/core/services/validators/archivable_issues_validator.py +71 -0
  459. roadmap/core/services/validators/archivable_milestones_validator.py +69 -0
  460. roadmap/core/services/validators/backup_validator.py +89 -0
  461. roadmap/core/services/validators/data_integrity_validator.py +84 -0
  462. roadmap/core/services/validators/duplicate_issues_validator.py +75 -0
  463. roadmap/core/services/validators/duplicate_milestones_validator.py +141 -0
  464. roadmap/core/services/validators/folder_structure_validator.py +165 -0
  465. roadmap/core/services/validators/health_status_utils.py +28 -0
  466. roadmap/core/services/validators/milestone_naming_validator.py +146 -0
  467. roadmap/core/services/validators/missing_headlines_validator.py +97 -0
  468. roadmap/core/services/validators/orphaned_issues_validator.py +142 -0
  469. roadmap/core/services/validators/orphaned_milestones_validator.py +85 -0
  470. roadmap/core/utils/git_remote_parser.py +83 -0
  471. roadmap/core/utils/github_urls.py +66 -0
  472. roadmap/domain/validation.py +30 -0
  473. roadmap/infrastructure/__init__.py +55 -0
  474. roadmap/infrastructure/coordination/__init__.py +8 -0
  475. roadmap/infrastructure/coordination/coordinator_params.py +82 -0
  476. roadmap/infrastructure/coordination/core.py +446 -0
  477. roadmap/infrastructure/coordination/git_coordinator.py +160 -0
  478. roadmap/infrastructure/coordination/initialization.py +348 -0
  479. roadmap/infrastructure/coordination/issue_coordinator.py +154 -0
  480. roadmap/infrastructure/coordination/issue_operations.py +456 -0
  481. roadmap/infrastructure/coordination/milestone_coordinator.py +135 -0
  482. roadmap/infrastructure/coordination/milestone_operations.py +201 -0
  483. roadmap/infrastructure/coordination/project_coordinator.py +113 -0
  484. roadmap/infrastructure/coordination/project_operations.py +144 -0
  485. roadmap/infrastructure/coordination/team_coordinator.py +107 -0
  486. roadmap/infrastructure/coordination/user_operations.py +150 -0
  487. roadmap/infrastructure/coordination/validation_coordinator.py +249 -0
  488. roadmap/infrastructure/coordination_gateway.py +152 -0
  489. roadmap/infrastructure/git/__init__.py +6 -0
  490. roadmap/infrastructure/git/git_integration_ops.py +234 -0
  491. roadmap/infrastructure/git_gateway.py +35 -0
  492. roadmap/infrastructure/github_gateway.py +52 -0
  493. roadmap/infrastructure/maintenance/__init__.py +5 -0
  494. roadmap/infrastructure/maintenance/cleanup.py +483 -0
  495. roadmap/infrastructure/observability/__init__.py +7 -0
  496. roadmap/infrastructure/observability/health.py +196 -0
  497. roadmap/infrastructure/observability/health_formatter.py +73 -0
  498. roadmap/infrastructure/observability/specialized_health_checkers.py +188 -0
  499. roadmap/infrastructure/persistence_gateway.py +164 -0
  500. roadmap/infrastructure/security/__init__.py +1 -0
  501. roadmap/infrastructure/security/credentials.py +489 -0
  502. roadmap/infrastructure/sync_gateway.py +94 -0
  503. roadmap/infrastructure/validation/__init__.py +7 -0
  504. roadmap/infrastructure/validation/file_enumeration.py +219 -0
  505. roadmap/infrastructure/validation/github_assignee_validator.py +47 -0
  506. roadmap/infrastructure/validation/github_validator.py +101 -0
  507. roadmap/infrastructure/validation/milestone_consistency_validator.py +96 -0
  508. roadmap/infrastructure/validation/vanilla_assignee_validator.py +37 -0
  509. roadmap/infrastructure/validation_gateway.py +96 -0
  510. roadmap/presentation/formatters/sync_metrics_formatter.py +355 -0
  511. roadmap/settings.py +345 -0
  512. roadmap/templates/github_workflows/ci-cd-roadmap.yml +241 -0
  513. roadmap/templates/github_workflows/issue-lifecycle.yml +236 -0
  514. roadmap/templates/github_workflows/roadmap-integration.yml +137 -0
  515. roadmap/templates/github_workflows/roadmap-starter.yml +59 -0
  516. roadmap/version.py +381 -0
  517. roadmap_cli-0.1.1.dist-info/METADATA +448 -0
  518. roadmap_cli-0.1.1.dist-info/RECORD +521 -0
  519. roadmap_cli-0.1.1.dist-info/WHEEL +4 -0
  520. roadmap_cli-0.1.1.dist-info/entry_points.txt +2 -0
  521. roadmap_cli-0.1.1.dist-info/licenses/LICENSE.md +21 -0
@@ -0,0 +1,162 @@
1
+ """Detects changes for GitHub linked entities."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from roadmap.core.domain import Issue
7
+ from roadmap.core.services.github.github_issue_client import GitHubIssueClient
8
+ from roadmap.core.services.sync.sync_report import IssueChange
9
+
10
+
11
+ class GitHubChangeDetector:
12
+ """Detects what has changed for GitHub-linked entities."""
13
+
14
+ def __init__(self, github_client: GitHubIssueClient):
15
+ """Initialize detector with GitHub client.
16
+
17
+ Args:
18
+ github_client: Client for fetching GitHub issues
19
+ """
20
+ self.github_client = github_client
21
+
22
+ def detect_issue_changes(
23
+ self,
24
+ local_issue: Issue,
25
+ owner: str,
26
+ repo: str,
27
+ last_sync_time: datetime | None = None,
28
+ ) -> IssueChange:
29
+ """Detect changes for a single linked issue.
30
+
31
+ Args:
32
+ local_issue: Local issue to check
33
+ owner: GitHub repository owner
34
+ repo: GitHub repository name
35
+ last_sync_time: When the issue was last synced
36
+
37
+ Returns:
38
+ IssueChange with detected changes
39
+ """
40
+ change = IssueChange(
41
+ issue_id=local_issue.id,
42
+ title=local_issue.title,
43
+ last_sync_time=last_sync_time,
44
+ )
45
+
46
+ try:
47
+ if not owner or not repo:
48
+ change.github_changes = {
49
+ "error": "GitHub config incomplete (owner/repo required)"
50
+ }
51
+ return change
52
+
53
+ if not local_issue.github_issue:
54
+ change.github_changes = {"error": "Issue not linked to GitHub"}
55
+ return change
56
+
57
+ # Fetch GitHub issue
58
+ github_issue_number = self._parse_github_issue_number(
59
+ local_issue.github_issue
60
+ )
61
+ github_issue = self.github_client.fetch_issue(
62
+ owner, repo, github_issue_number
63
+ )
64
+
65
+ if not github_issue:
66
+ change.github_changes = {"issue": "deleted on GitHub"}
67
+ return change
68
+
69
+ # Detect local and GitHub changes
70
+ change.local_changes = self._detect_local_changes(local_issue)
71
+ change.github_changes = self._detect_github_changes(
72
+ local_issue, github_issue
73
+ )
74
+
75
+ # Determine conflict status
76
+ if change.local_changes and change.github_changes:
77
+ change.has_conflict = True
78
+
79
+ except Exception as e:
80
+ change.github_changes = {"error": str(e)}
81
+
82
+ return change
83
+
84
+ def detect_milestone_changes(self, _local_milestone: Any) -> IssueChange | None:
85
+ """Detect changes for a milestone.
86
+
87
+ Args:
88
+ _local_milestone: Local milestone to check
89
+
90
+ Returns:
91
+ IssueChange with detected changes, or None if no changes
92
+ """
93
+ # Placeholder: Milestone detection logic to be extracted
94
+ # from _detect_milestone_changes
95
+ return None
96
+
97
+ @staticmethod
98
+ def _parse_github_issue_number(github_issue: Any) -> int:
99
+ """Parse GitHub issue number from string or int.
100
+
101
+ Args:
102
+ github_issue: GitHub issue number (string or int)
103
+
104
+ Returns:
105
+ GitHub issue number as integer
106
+
107
+ Raises:
108
+ ValueError: If cannot parse issue number
109
+ """
110
+ if isinstance(github_issue, str):
111
+ return int(github_issue)
112
+ return github_issue
113
+
114
+ @staticmethod
115
+ def _detect_local_changes(_issue: Issue) -> dict[str, Any]:
116
+ """Detect changes made locally to the issue.
117
+
118
+ Args:
119
+ _issue: Issue to check
120
+
121
+ Returns:
122
+ Dict of detected local changes
123
+ """
124
+ changes = {}
125
+
126
+ # Check for local modifications
127
+ # (Placeholder - extract from _detect_local_changes)
128
+ # Note: Issue model doesn't track dirty state, so no local changes detected here
129
+ # This is a placeholder for future enhancement when tracking changes
130
+
131
+ return changes
132
+
133
+ @staticmethod
134
+ def _detect_github_changes(
135
+ local_issue: Issue, github_issue: dict[str, Any]
136
+ ) -> dict[str, Any]:
137
+ """Detect changes made on GitHub.
138
+
139
+ Args:
140
+ local_issue: Local issue version
141
+ github_issue: GitHub issue version
142
+
143
+ Returns:
144
+ Dict of detected GitHub changes
145
+ """
146
+ changes = {}
147
+
148
+ # Compare title
149
+ if github_issue.get("title") != local_issue.title:
150
+ changes["title"] = github_issue.get("title")
151
+
152
+ # Compare content
153
+ gh_body = github_issue.get("body", "")
154
+ if gh_body != (local_issue.content or ""):
155
+ changes["content"] = gh_body
156
+
157
+ # Compare status
158
+ gh_status = "closed" if github_issue.get("state") == "closed" else "open"
159
+ if gh_status != local_issue.status:
160
+ changes["status"] = gh_status
161
+
162
+ return changes
@@ -0,0 +1,148 @@
1
+ """GitHub configuration validation service."""
2
+
3
+ from pathlib import Path
4
+
5
+ import requests
6
+
7
+ from roadmap.core.services.github.github_integration_service import (
8
+ GitHubIntegrationService,
9
+ )
10
+
11
+
12
+ class GitHubConfigValidator:
13
+ """Validates GitHub configuration before operations."""
14
+
15
+ def __init__(self, config_path: Path):
16
+ """Initialize validator with config path.
17
+
18
+ Args:
19
+ config_path: Path to .roadmap directory
20
+ """
21
+ self.config_path = config_path
22
+ self.config_file = config_path / "config.yaml"
23
+ self.service = GitHubIntegrationService(config_path, self.config_file)
24
+
25
+ def validate_config(self) -> tuple[bool, str | None]:
26
+ """Validate GitHub configuration.
27
+
28
+ Returns:
29
+ Tuple of (is_valid, error_message)
30
+ """
31
+ # Check if config exists
32
+ token, _, repo = self.service.get_github_config()
33
+ if not token or not repo:
34
+ return (
35
+ False,
36
+ "GitHub not configured. Run: roadmap init --github-repo owner/repo --github-token <token>",
37
+ )
38
+
39
+ # Check required fields
40
+ if not repo:
41
+ return False, "GitHub repo not configured in config.yaml"
42
+
43
+ if not token:
44
+ return False, "GitHub token not configured in config.yaml"
45
+
46
+ # Validate token format (basic check)
47
+ if not isinstance(token, str) or len(token) < 10:
48
+ return False, "GitHub token appears invalid (too short)"
49
+
50
+ return True, None
51
+
52
+ def validate_token(self) -> tuple[bool, str | None]:
53
+ """Validate GitHub token accessibility.
54
+
55
+ Returns:
56
+ Tuple of (is_valid, error_message)
57
+ """
58
+ token, _, _ = self.service.get_github_config()
59
+ if not token:
60
+ return False, "GitHub token not set"
61
+
62
+ try:
63
+ # Test token by making a simple API call
64
+ headers = {
65
+ "Authorization": f"token {token}",
66
+ "Accept": "application/vnd.github.v3+json",
67
+ }
68
+ response = requests.get(
69
+ "https://api.github.com/user",
70
+ headers=headers,
71
+ timeout=5,
72
+ )
73
+
74
+ if response.status_code == 401:
75
+ return False, "GitHub token is invalid or expired"
76
+ elif response.status_code == 403:
77
+ return False, "GitHub token has insufficient permissions"
78
+ elif response.status_code == 200:
79
+ return True, None
80
+ else:
81
+ return (
82
+ False,
83
+ f"GitHub API error: {response.status_code}",
84
+ )
85
+ except requests.RequestException as e:
86
+ return False, f"Failed to reach GitHub: {str(e)}"
87
+ except Exception as e:
88
+ return False, f"Unexpected error validating token: {str(e)}"
89
+
90
+ def validate_repo_access(self) -> tuple[bool, str | None]:
91
+ """Validate access to configured repository.
92
+
93
+ Returns:
94
+ Tuple of (is_valid, error_message)
95
+ """
96
+ token, _, repo = self.service.get_github_config()
97
+ if not token or not repo:
98
+ return False, "GitHub config incomplete"
99
+
100
+ try:
101
+ # Test repo access
102
+ headers = {
103
+ "Authorization": f"token {token}",
104
+ "Accept": "application/vnd.github.v3+json",
105
+ }
106
+ url = f"https://api.github.com/repos/{repo}"
107
+ response = requests.get(url, headers=headers, timeout=5)
108
+
109
+ if response.status_code == 401:
110
+ return False, "GitHub token is invalid or expired"
111
+ elif response.status_code == 403:
112
+ return False, f"Access denied to repository {repo}"
113
+ elif response.status_code == 404:
114
+ return False, f"Repository not found: {repo}"
115
+ elif response.status_code == 200:
116
+ return True, None
117
+ else:
118
+ return (
119
+ False,
120
+ f"GitHub API error: {response.status_code}",
121
+ )
122
+ except requests.RequestException as e:
123
+ return False, f"Failed to reach GitHub: {str(e)}"
124
+ except Exception as e:
125
+ return False, f"Unexpected error validating repo: {str(e)}"
126
+
127
+ def validate_all(self) -> tuple[bool, str | None]:
128
+ """Run all validations.
129
+
130
+ Returns:
131
+ Tuple of (is_valid, error_message)
132
+ """
133
+ # Config validation
134
+ is_valid, error = self.validate_config()
135
+ if not is_valid:
136
+ return False, error
137
+
138
+ # Token validation
139
+ is_valid, error = self.validate_token()
140
+ if not is_valid:
141
+ return False, error
142
+
143
+ # Repo access validation
144
+ is_valid, error = self.validate_repo_access()
145
+ if not is_valid:
146
+ return False, error
147
+
148
+ return True, None
@@ -0,0 +1,179 @@
1
+ """GitHub conflict detection service."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from roadmap.common.datetime_parser import UnifiedDateTimeParser
7
+ from roadmap.core.domain.issue import Issue
8
+ from roadmap.core.services.github.github_integration_service import (
9
+ GitHubIntegrationService,
10
+ )
11
+ from roadmap.core.services.github.github_issue_client import GitHubIssueClient
12
+
13
+
14
+ class GitHubConflictDetector:
15
+ """Detects conflicts between local and GitHub issues."""
16
+
17
+ def __init__(self, integration_service: GitHubIntegrationService):
18
+ """Initialize conflict detector.
19
+
20
+ Args:
21
+ integration_service: GitHub integration service instance
22
+ """
23
+ self.service = integration_service
24
+ token, owner, repo = integration_service.get_github_config()
25
+ self.owner = owner
26
+ self.repo = repo
27
+ self.client = GitHubIssueClient(token)
28
+
29
+ def detect_conflicts(
30
+ self, local_issue: Issue, github_number: int
31
+ ) -> dict[str, Any]:
32
+ """Detect conflicts between local and GitHub versions.
33
+
34
+ Args:
35
+ local_issue: Local issue entity
36
+ github_number: GitHub issue number
37
+
38
+ Returns:
39
+ Dict with conflict information:
40
+ {
41
+ 'has_conflicts': bool,
42
+ 'local_modified': bool,
43
+ 'github_modified': bool,
44
+ 'last_sync': datetime | None,
45
+ 'warnings': List[str]
46
+ }
47
+ """
48
+ conflicts = {
49
+ "has_conflicts": False,
50
+ "local_modified": False,
51
+ "github_modified": False,
52
+ "last_sync": None,
53
+ "warnings": [],
54
+ }
55
+
56
+ # Get last sync time from issue metadata if available
57
+ last_sync = self._get_last_sync_time(local_issue)
58
+ conflicts["last_sync"] = last_sync
59
+
60
+ # If no last sync recorded, can't detect conflicts
61
+ if not last_sync:
62
+ conflicts["warnings"].append(
63
+ "No sync history available. First sync will update all fields."
64
+ )
65
+ return conflicts
66
+
67
+ # Check if we have required GitHub config
68
+ if not self.owner or not self.repo:
69
+ conflicts["warnings"].append(
70
+ "GitHub configuration missing. Cannot check for GitHub changes."
71
+ )
72
+ return conflicts
73
+
74
+ # Check if GitHub issue was modified after last sync
75
+ try:
76
+ github_issue = self.client.fetch_issue(self.owner, self.repo, github_number)
77
+ github_updated_at = self._parse_github_timestamp(
78
+ github_issue.get("updated_at")
79
+ )
80
+
81
+ if github_updated_at and github_updated_at > last_sync:
82
+ conflicts["github_modified"] = True
83
+ conflicts["warnings"].append(
84
+ "GitHub issue was modified after last sync"
85
+ )
86
+ except Exception as e:
87
+ conflicts["warnings"].append(
88
+ f"Could not check GitHub for changes: {str(e)}"
89
+ )
90
+
91
+ # Check if local issue was modified after last sync
92
+ if self._is_local_modified_after_sync(local_issue, last_sync):
93
+ conflicts["local_modified"] = True
94
+ conflicts["warnings"].append("Local issue was modified after last sync")
95
+
96
+ # Set has_conflicts flag if both were modified
97
+ if conflicts["local_modified"] and conflicts["github_modified"]:
98
+ conflicts["has_conflicts"] = True
99
+ conflicts["warnings"].append(
100
+ "⚠️ Both local and GitHub versions have changes. Review carefully before syncing."
101
+ )
102
+
103
+ return conflicts
104
+
105
+ def _get_last_sync_time(self, issue: Issue) -> datetime | None:
106
+ """Get last sync timestamp from issue.
107
+
108
+ Args:
109
+ issue: Issue entity
110
+
111
+ Returns:
112
+ Last sync datetime or None
113
+ """
114
+ # Check if issue has sync metadata with sync timestamp
115
+ if (
116
+ issue.github_sync_metadata
117
+ and "sync_timestamp" in issue.github_sync_metadata
118
+ ):
119
+ return issue.github_sync_metadata["sync_timestamp"]
120
+
121
+ # Check issue updated_at as fallback
122
+ if issue.updated:
123
+ return issue.updated
124
+
125
+ return None
126
+
127
+ def _is_local_modified_after_sync(self, issue: Issue, last_sync: datetime) -> bool:
128
+ """Check if local issue was modified after sync time.
129
+
130
+ Args:
131
+ issue: Issue entity
132
+ last_sync: Last sync timestamp
133
+
134
+ Returns:
135
+ True if modified after sync
136
+ """
137
+ if not issue.updated:
138
+ return False
139
+
140
+ updated_at = issue.updated
141
+ return updated_at > last_sync
142
+
143
+ def _parse_github_timestamp(self, timestamp: str | None) -> datetime | None:
144
+ """Parse GitHub API timestamp.
145
+
146
+ Args:
147
+ timestamp: GitHub timestamp string
148
+
149
+ Returns:
150
+ Parsed datetime or None
151
+ """
152
+ if not timestamp:
153
+ return None
154
+
155
+ try:
156
+ return UnifiedDateTimeParser.parse_github_timestamp(timestamp)
157
+ except (ValueError, AttributeError):
158
+ return None
159
+
160
+ def get_conflict_summary(self, conflicts: dict[str, Any]) -> str:
161
+ """Get human-readable conflict summary.
162
+
163
+ Args:
164
+ conflicts: Conflicts dict from detect_conflicts()
165
+
166
+ Returns:
167
+ Summary string
168
+ """
169
+ if not conflicts["warnings"]:
170
+ return "No conflicts detected. Safe to sync."
171
+
172
+ summary = "Conflict Detection Summary:\n"
173
+ for warning in conflicts["warnings"]:
174
+ summary += f" • {warning}\n"
175
+
176
+ if conflicts["has_conflicts"]:
177
+ summary += "\nRecommendation: Review changes manually before syncing."
178
+
179
+ return summary
@@ -0,0 +1,51 @@
1
+ """Classifies GitHub entities (active/archived)."""
2
+
3
+ from typing import Any, TypeVar
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ class GitHubEntityClassifier:
9
+ """Separates entities into active and archived categories."""
10
+
11
+ @staticmethod
12
+ def classify_entity_state(entity: Any) -> bool:
13
+ """Determine if entity is archived.
14
+
15
+ Args:
16
+ entity: Entity object to check
17
+
18
+ Returns:
19
+ True if archived, False if active
20
+
21
+ Note:
22
+ Handles mocks safely by checking isinstance
23
+ """
24
+ is_archived = False
25
+ if hasattr(entity, "archived"):
26
+ archived_attr = getattr(entity, "archived", False)
27
+ # Check if it's an actual boolean value, not a mock
28
+ if isinstance(archived_attr, bool):
29
+ is_archived = archived_attr
30
+ return is_archived
31
+
32
+ @classmethod
33
+ def separate_by_state(cls, entities: list[T]) -> tuple[list[T], list[T]]:
34
+ """Separate entities into active and archived.
35
+
36
+ Args:
37
+ entities: List of entities to classify
38
+
39
+ Returns:
40
+ Tuple of (active_entities, archived_entities)
41
+ """
42
+ active = []
43
+ archived = []
44
+
45
+ for entity in entities:
46
+ if cls.classify_entity_state(entity):
47
+ archived.append(entity)
48
+ else:
49
+ active.append(entity)
50
+
51
+ return active, archived