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,396 @@
1
+ """Result type for functional error handling.
2
+
3
+ This module provides a Result[T, E] type for railway-oriented programming,
4
+ enabling explicit error handling without exceptions. Based on Rust's Result type.
5
+
6
+ Examples:
7
+ >>> def divide(a: int, b: int) -> Result[float, str]:
8
+ ... if b == 0:
9
+ ... return Err("Division by zero")
10
+ ... return Ok(a / b)
11
+ ...
12
+ >>> result = divide(10, 2)
13
+ >>> if result.is_ok():
14
+ ... print(f"Success: {result.unwrap()}")
15
+ ...
16
+ >>> result = divide(10, 0)
17
+ >>> if result.is_err():
18
+ ... print(f"Error: {result.unwrap_err()}")
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from collections.abc import Callable
24
+ from dataclasses import dataclass
25
+ from typing import Any, Generic, NoReturn, TypeVar, Union
26
+
27
+ T = TypeVar("T") # Success type
28
+ E = TypeVar("E") # Error type
29
+ U = TypeVar("U") # Mapped success type
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class Ok(Generic[T]):
34
+ """Success variant of Result type.
35
+
36
+ Contains a successful value of type T.
37
+
38
+ Attributes:
39
+ value: The successful result value
40
+ """
41
+
42
+ value: T
43
+
44
+ def is_ok(self) -> bool:
45
+ """Check if this is an Ok variant.
46
+
47
+ Returns:
48
+ Always True for Ok
49
+ """
50
+ return True
51
+
52
+ def is_err(self) -> bool:
53
+ """Check if this is an Err variant.
54
+
55
+ Returns:
56
+ Always False for Ok
57
+ """
58
+ return False
59
+
60
+ def unwrap(self) -> T:
61
+ """Extract the success value.
62
+
63
+ Returns:
64
+ The contained value
65
+ """
66
+ return self.value
67
+
68
+ def unwrap_err(self) -> NoReturn:
69
+ """Attempt to extract error (raises exception).
70
+
71
+ Raises:
72
+ ValueError: Always, since Ok doesn't contain an error
73
+ """
74
+ raise ValueError(f"Called unwrap_err() on Ok value: {self.value}")
75
+
76
+ def unwrap_or(self, default: T) -> T:
77
+ """Extract value or return default.
78
+
79
+ Args:
80
+ default: Value to return if this is Err (unused)
81
+
82
+ Returns:
83
+ The contained value
84
+ """
85
+ return self.value
86
+
87
+ def unwrap_or_else(self, op: Callable[[E], T]) -> T:
88
+ """Extract value or compute from error.
89
+
90
+ Args:
91
+ op: Function to compute default from error (unused)
92
+
93
+ Returns:
94
+ The contained value
95
+ """
96
+ return self.value
97
+
98
+ def map(self, op: Callable[[T], U]) -> Result[U, E]:
99
+ """Map the success value through a function.
100
+
101
+ Args:
102
+ op: Function to transform the value
103
+
104
+ Returns:
105
+ Ok with transformed value
106
+ """
107
+ return Ok(op(self.value))
108
+
109
+ def map_err(self, op: Callable[[E], Any]) -> Result[T, Any]:
110
+ """Map the error through a function (no-op for Ok).
111
+
112
+ Args:
113
+ op: Function to transform error (unused)
114
+
115
+ Returns:
116
+ Self unchanged
117
+ """
118
+ return self
119
+
120
+ def and_then(self, op: Callable[[T], Result[U, E]]) -> Result[U, E]:
121
+ """Chain another Result-returning operation.
122
+
123
+ Also known as flatMap or bind. Enables railway-oriented programming.
124
+
125
+ Args:
126
+ op: Function that takes success value and returns new Result
127
+
128
+ Returns:
129
+ Result from applying op to contained value
130
+ """
131
+ return op(self.value)
132
+
133
+ def or_else(self, op: Callable[[E], Result[T, Any]]) -> Result[T, Any]:
134
+ """Provide alternative on error (no-op for Ok).
135
+
136
+ Args:
137
+ op: Function to compute alternative Result (unused)
138
+
139
+ Returns:
140
+ Self unchanged
141
+ """
142
+ return self
143
+
144
+
145
+ @dataclass(frozen=True)
146
+ class Err(Generic[E]):
147
+ """Error variant of Result type.
148
+
149
+ Contains an error value of type E.
150
+
151
+ Attributes:
152
+ error: The error value
153
+ """
154
+
155
+ error: E
156
+
157
+ def is_ok(self) -> bool:
158
+ """Check if this is an Ok variant.
159
+
160
+ Returns:
161
+ Always False for Err
162
+ """
163
+ return False
164
+
165
+ def is_err(self) -> bool:
166
+ """Check if this is an Err variant.
167
+
168
+ Returns:
169
+ Always True for Err
170
+ """
171
+ return True
172
+
173
+ def unwrap(self) -> NoReturn:
174
+ """Attempt to extract value (raises exception).
175
+
176
+ Raises:
177
+ ValueError: Always, since Err doesn't contain a value
178
+ """
179
+ raise ValueError(f"Called unwrap() on Err value: {self.error}")
180
+
181
+ def unwrap_err(self) -> E:
182
+ """Extract the error value.
183
+
184
+ Returns:
185
+ The contained error
186
+ """
187
+ return self.error
188
+
189
+ def unwrap_or(self, default: T) -> T:
190
+ """Extract value or return default.
191
+
192
+ Args:
193
+ default: Value to return since this is Err
194
+
195
+ Returns:
196
+ The default value
197
+ """
198
+ return default
199
+
200
+ def unwrap_or_else(self, op: Callable[[E], T]) -> T:
201
+ """Extract value or compute from error.
202
+
203
+ Args:
204
+ op: Function to compute default from error
205
+
206
+ Returns:
207
+ Result of applying op to the error
208
+ """
209
+ return op(self.error)
210
+
211
+ def map(self, op: Callable[[T], U]) -> Result[U, E]:
212
+ """Map the success value through a function (no-op for Err).
213
+
214
+ Args:
215
+ op: Function to transform value (unused)
216
+
217
+ Returns:
218
+ Self unchanged (with correct type)
219
+ """
220
+ return Err(self.error)
221
+
222
+ def map_err(self, op: Callable[[E], Any]) -> Result[T, Any]:
223
+ """Map the error through a function.
224
+
225
+ Args:
226
+ op: Function to transform the error
227
+
228
+ Returns:
229
+ Err with transformed error
230
+ """
231
+ return Err(op(self.error))
232
+
233
+ def and_then(self, op: Callable[[T], Result[U, E]]) -> Result[U, E]:
234
+ """Chain another Result-returning operation (no-op for Err).
235
+
236
+ Args:
237
+ op: Function to chain (unused)
238
+
239
+ Returns:
240
+ Self unchanged (with correct type)
241
+ """
242
+ return Err(self.error)
243
+
244
+ def or_else(self, op: Callable[[E], Result[T, Any]]) -> Result[T, Any]:
245
+ """Provide alternative on error.
246
+
247
+ Args:
248
+ op: Function to compute alternative Result from error
249
+
250
+ Returns:
251
+ Result from applying op to error
252
+ """
253
+ return op(self.error)
254
+
255
+
256
+ # Type alias for Result[T, E]
257
+ Result = Union[Ok[T], Err[E]]
258
+
259
+
260
+ # Helper functions for working with Results
261
+
262
+
263
+ def wrap_result(func: Callable[..., T]) -> Callable[..., Result[T, Exception]]:
264
+ """Decorator to convert exception-raising functions to Result-returning.
265
+
266
+ Catches any exception and returns it as Err, or Ok on success.
267
+
268
+ Args:
269
+ func: Function that may raise exceptions
270
+
271
+ Returns:
272
+ Wrapped function that returns Result[T, Exception]
273
+
274
+ Example:
275
+ >>> @wrap_result
276
+ ... def may_fail(x: int) -> int:
277
+ ... if x < 0:
278
+ ... raise ValueError("Negative")
279
+ ... return x * 2
280
+ ...
281
+ >>> result = may_fail(5) # Ok(10)
282
+ >>> result = may_fail(-1) # Err(ValueError("Negative"))
283
+ """
284
+
285
+ def wrapper(*args: Any, **kwargs: Any) -> Result[T, Exception]:
286
+ try:
287
+ return Ok(func(*args, **kwargs))
288
+ except Exception as e:
289
+ return Err(e)
290
+
291
+ return wrapper
292
+
293
+
294
+ def collect_results(results: list[Result[T, E]]) -> Result[list[T], E]:
295
+ """Collect a list of Results into a Result of list.
296
+
297
+ Returns Ok with list of all success values if all Results are Ok.
298
+ Returns the first Err if any Result is Err.
299
+
300
+ Args:
301
+ results: List of Result values
302
+
303
+ Returns:
304
+ Ok with list of values, or first Err encountered
305
+
306
+ Example:
307
+ >>> results = [Ok(1), Ok(2), Ok(3)]
308
+ >>> collect_results(results) # Ok([1, 2, 3])
309
+ >>>
310
+ >>> results = [Ok(1), Err("fail"), Ok(3)]
311
+ >>> collect_results(results) # Err("fail")
312
+ """
313
+ values: list[T] = []
314
+ for result in results:
315
+ if result.is_err():
316
+ return result # type: ignore[return-value]
317
+ values.append(result.unwrap())
318
+ return Ok(values)
319
+
320
+
321
+ def partition_results(results: list[Result[T, E]]) -> tuple[list[T], list[E]]:
322
+ """Partition a list of Results into successes and errors.
323
+
324
+ Args:
325
+ results: List of Result values
326
+
327
+ Returns:
328
+ Tuple of (success_values, error_values)
329
+
330
+ Example:
331
+ >>> results = [Ok(1), Err("a"), Ok(2), Err("b")]
332
+ >>> successes, errors = partition_results(results)
333
+ >>> successes # [1, 2]
334
+ >>> errors # ["a", "b"]
335
+ """
336
+ successes: list[T] = []
337
+ errors: list[E] = []
338
+ for result in results:
339
+ if result.is_ok():
340
+ successes.append(result.unwrap())
341
+ else:
342
+ errors.append(result.unwrap_err())
343
+ return successes, errors
344
+
345
+
346
+ def all_ok(results: list[Result[T, E]]) -> bool:
347
+ """Check if all Results in a list are Ok.
348
+
349
+ Args:
350
+ results: List of Result values
351
+
352
+ Returns:
353
+ True if all are Ok, False if any are Err
354
+ """
355
+ return all(r.is_ok() for r in results)
356
+
357
+
358
+ def any_err(results: list[Result[T, E]]) -> bool:
359
+ """Check if any Results in a list are Err.
360
+
361
+ Args:
362
+ results: List of Result values
363
+
364
+ Returns:
365
+ True if any are Err, False if all are Ok
366
+ """
367
+ return any(r.is_err() for r in results)
368
+
369
+
370
+ def first_err(results: list[Result[T, E]]) -> Result[None, E]:
371
+ """Get the first error from a list of Results.
372
+
373
+ Args:
374
+ results: List of Result values
375
+
376
+ Returns:
377
+ First Err found, or Ok(None) if all are Ok
378
+ """
379
+ for result in results:
380
+ if result.is_err():
381
+ return Err(result.unwrap_err())
382
+ return Ok(None)
383
+
384
+
385
+ # Export public API
386
+ __all__ = [
387
+ "Ok",
388
+ "Err",
389
+ "Result",
390
+ "wrap_result",
391
+ "collect_results",
392
+ "partition_results",
393
+ "all_ok",
394
+ "any_err",
395
+ "first_err",
396
+ ]
@@ -0,0 +1,48 @@
1
+ """Security utilities for roadmap CLI application.
2
+
3
+ This package provides security functionality organized by concern:
4
+ - exceptions: Security exception types
5
+ - logging: Security event logging
6
+ - path_validation: Path validation and safety checks
7
+ - file_operations: Secure file and directory operations
8
+ - filename_sanitization: Filename sanitization
9
+ - temp_files: Secure temporary file handling
10
+ - export_cleanup: Export validation and backup cleanup
11
+
12
+ For backward compatibility, all public classes and functions are re-exported
13
+ from the root module.
14
+ """
15
+
16
+ from .exceptions import PathValidationError, SecurityError
17
+ from .export_cleanup import cleanup_old_backups, validate_export_size
18
+ from .file_operations import (
19
+ create_secure_directory,
20
+ create_secure_file,
21
+ secure_file_permissions,
22
+ )
23
+ from .filename_sanitization import sanitize_filename
24
+ from .logging import log_security_event, security_logger
25
+ from .path_validation import validate_path
26
+ from .temp_files import create_secure_temp_file
27
+
28
+ __all__ = [
29
+ # Exceptions
30
+ "SecurityError",
31
+ "PathValidationError",
32
+ # Logging
33
+ "log_security_event",
34
+ "security_logger",
35
+ # Path validation
36
+ "validate_path",
37
+ # File operations
38
+ "create_secure_file",
39
+ "create_secure_directory",
40
+ "secure_file_permissions",
41
+ # Filename sanitization
42
+ "sanitize_filename",
43
+ # Temp files
44
+ "create_secure_temp_file",
45
+ # Export/backup
46
+ "validate_export_size",
47
+ "cleanup_old_backups",
48
+ ]
@@ -0,0 +1,13 @@
1
+ """Security exceptions."""
2
+
3
+
4
+ class SecurityError(Exception):
5
+ """Base exception for security-related errors."""
6
+
7
+ pass
8
+
9
+
10
+ class PathValidationError(SecurityError):
11
+ """Exception raised for path validation failures."""
12
+
13
+ pass
@@ -0,0 +1,79 @@
1
+ """Export validation and backup cleanup utilities."""
2
+
3
+ import time
4
+ from pathlib import Path
5
+
6
+ from .exceptions import SecurityError
7
+ from .logging import log_security_event
8
+
9
+
10
+ def validate_export_size(file_path: Path, max_size_mb: int = 100) -> None:
11
+ """Validate that an export file isn't too large.
12
+
13
+ Args:
14
+ file_path: Path to the export file
15
+ max_size_mb: Maximum allowed size in MB
16
+
17
+ Raises:
18
+ SecurityError: If file is too large
19
+ """
20
+ if not file_path.exists():
21
+ return
22
+
23
+ file_size_mb = file_path.stat().st_size / (1024 * 1024)
24
+
25
+ if file_size_mb > max_size_mb:
26
+ log_security_event(
27
+ "large_export_detected",
28
+ {
29
+ "path": str(file_path),
30
+ "size_mb": file_size_mb,
31
+ "max_size_mb": max_size_mb,
32
+ },
33
+ )
34
+ raise SecurityError(
35
+ f"Export file too large: {file_size_mb:.1f}MB > {max_size_mb}MB"
36
+ )
37
+
38
+
39
+ def cleanup_old_backups(backup_dir: Path, retention_days: int = 30) -> int:
40
+ """Clean up old backup files for security.
41
+
42
+ Args:
43
+ backup_dir: Directory containing backup files
44
+ retention_days: Number of days to retain backups
45
+
46
+ Returns:
47
+ Number of files cleaned up
48
+ """
49
+ if not backup_dir.exists():
50
+ return 0
51
+
52
+ cutoff_time = time.time() - (retention_days * 24 * 60 * 60)
53
+ cleaned_count = 0
54
+
55
+ try:
56
+ for backup_file in backup_dir.glob("*.backup*"):
57
+ if backup_file.stat().st_mtime < cutoff_time:
58
+ backup_file.unlink()
59
+ cleaned_count += 1
60
+
61
+ log_security_event(
62
+ "backup_cleaned",
63
+ {
64
+ "path": str(backup_file),
65
+ "age_days": (time.time() - backup_file.stat().st_mtime)
66
+ / (24 * 60 * 60),
67
+ },
68
+ )
69
+
70
+ if cleaned_count > 0:
71
+ log_security_event(
72
+ "backup_cleanup_completed",
73
+ {"files_cleaned": cleaned_count, "retention_days": retention_days},
74
+ )
75
+
76
+ except Exception as e:
77
+ log_security_event("backup_cleanup_failed", {"error": str(e)})
78
+
79
+ return cleaned_count
@@ -0,0 +1,104 @@
1
+ """Secure file and directory operations."""
2
+
3
+ from contextlib import contextmanager
4
+ from pathlib import Path
5
+
6
+ from ..utils.file_utils import ensure_directory_exists
7
+ from .exceptions import SecurityError
8
+ from .logging import log_security_event
9
+
10
+
11
+ @contextmanager
12
+ def create_secure_file(
13
+ path: str | Path, mode: str = "w", permissions: int = 0o600, **kwargs
14
+ ):
15
+ """Create a file with secure permissions as a context manager.
16
+
17
+ Args:
18
+ path: Path to the file to create
19
+ mode: File open mode (default: 'w')
20
+ permissions: File permissions (default: owner read/write only)
21
+ **kwargs: Additional arguments passed to open()
22
+
23
+ Yields:
24
+ File object
25
+
26
+ Raises:
27
+ SecurityError: If file creation fails or permissions cannot be set
28
+ """
29
+ path = Path(path)
30
+
31
+ try:
32
+ # Create parent directories if they don't exist
33
+ ensure_directory_exists(path.parent)
34
+
35
+ # Open file with specified mode
36
+ with open(path, mode, **kwargs) as f:
37
+ # Set secure permissions after file is created
38
+ try:
39
+ path.chmod(permissions)
40
+ except (OSError, PermissionError) as e:
41
+ # Log warning but don't fail - permissions might not be changeable
42
+ log_security_event(
43
+ "permission_warning", {"path": str(path), "error": str(e)}
44
+ )
45
+
46
+ log_security_event(
47
+ "file_created", {"path": str(path), "permissions": oct(permissions)}
48
+ )
49
+
50
+ yield f
51
+
52
+ except Exception as e:
53
+ log_security_event("file_creation_failed", {"path": str(path), "error": str(e)})
54
+ raise SecurityError(f"Failed to create secure file {path}: {e}") from e
55
+
56
+
57
+ def create_secure_directory(path: Path, permissions: int = 0o700) -> None:
58
+ """Create a directory with secure permissions.
59
+
60
+ Args:
61
+ path: Path to the directory to create
62
+ permissions: Directory permissions (default: owner only)
63
+
64
+ Raises:
65
+ SecurityError: If directory creation fails
66
+ """
67
+ try:
68
+ ensure_directory_exists(path, permissions=permissions)
69
+ # Note: ensure_directory_exists already sets permissions
70
+
71
+ log_security_event(
72
+ "directory_created", {"path": str(path), "permissions": oct(permissions)}
73
+ )
74
+ except Exception as e:
75
+ log_security_event(
76
+ "directory_creation_failed", {"path": str(path), "error": str(e)}
77
+ )
78
+ raise SecurityError(f"Failed to create secure directory {path}: {e}") from e
79
+
80
+
81
+ def secure_file_permissions(path: Path, permissions: int = 0o600) -> None:
82
+ """Set secure permissions on an existing file.
83
+
84
+ Args:
85
+ path: Path to the file
86
+ permissions: Permissions to set (default: owner read/write only)
87
+
88
+ Raises:
89
+ SecurityError: If permission setting fails
90
+ """
91
+ try:
92
+ if not path.exists():
93
+ raise SecurityError(f"File does not exist: {path}")
94
+
95
+ path.chmod(permissions)
96
+
97
+ log_security_event(
98
+ "permissions_set", {"path": str(path), "permissions": oct(permissions)}
99
+ )
100
+ except Exception as e:
101
+ log_security_event(
102
+ "permission_setting_failed", {"path": str(path), "error": str(e)}
103
+ )
104
+ raise SecurityError(f"Failed to set secure permissions on {path}: {e}") from e
@@ -0,0 +1,54 @@
1
+ """Filename sanitization utilities."""
2
+
3
+ import os
4
+ import re
5
+
6
+ from .exceptions import SecurityError
7
+ from .logging import log_security_event
8
+
9
+
10
+ def sanitize_filename(filename: str, max_length: int = 255) -> str:
11
+ """Sanitize a filename to prevent security issues.
12
+
13
+ Args:
14
+ filename: Original filename
15
+ max_length: Maximum allowed filename length
16
+
17
+ Returns:
18
+ Sanitized filename
19
+ """
20
+ if not filename:
21
+ raise SecurityError("Filename cannot be empty")
22
+
23
+ # Remove or replace dangerous characters
24
+ # Windows: < > : " | ? * \0
25
+ # Unix: \0 /
26
+ # Generally dangerous: .. (relative path)
27
+
28
+ # Replace dangerous characters with underscores
29
+ sanitized = re.sub(r'[<>:"/\\|?*\0]', "_", filename)
30
+
31
+ # Replace relative path attempts
32
+ sanitized = sanitized.replace("..", "_")
33
+
34
+ # Remove leading/trailing whitespace and dots
35
+ sanitized = sanitized.strip(" .")
36
+
37
+ # Truncate to max length
38
+ if len(sanitized) > max_length:
39
+ name, ext = os.path.splitext(sanitized)
40
+ if ext:
41
+ max_name_length = max_length - len(ext)
42
+ sanitized = name[:max_name_length] + ext
43
+ else:
44
+ sanitized = sanitized[:max_length]
45
+
46
+ # Ensure we still have a valid filename
47
+ if not sanitized or sanitized in {".", ".."}:
48
+ sanitized = "safe_filename"
49
+
50
+ log_security_event(
51
+ "filename_sanitized", {"original": filename, "sanitized": sanitized}
52
+ )
53
+
54
+ return sanitized