crackerjack 0.18.2__py3-none-any.whl → 0.45.2__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 (533) hide show
  1. crackerjack/README.md +19 -0
  2. crackerjack/__init__.py +96 -2
  3. crackerjack/__main__.py +637 -138
  4. crackerjack/adapters/README.md +18 -0
  5. crackerjack/adapters/__init__.py +39 -0
  6. crackerjack/adapters/_output_paths.py +167 -0
  7. crackerjack/adapters/_qa_adapter_base.py +309 -0
  8. crackerjack/adapters/_tool_adapter_base.py +706 -0
  9. crackerjack/adapters/ai/README.md +65 -0
  10. crackerjack/adapters/ai/__init__.py +5 -0
  11. crackerjack/adapters/ai/claude.py +853 -0
  12. crackerjack/adapters/complexity/README.md +53 -0
  13. crackerjack/adapters/complexity/__init__.py +10 -0
  14. crackerjack/adapters/complexity/complexipy.py +641 -0
  15. crackerjack/adapters/dependency/__init__.py +22 -0
  16. crackerjack/adapters/dependency/pip_audit.py +418 -0
  17. crackerjack/adapters/format/README.md +72 -0
  18. crackerjack/adapters/format/__init__.py +11 -0
  19. crackerjack/adapters/format/mdformat.py +313 -0
  20. crackerjack/adapters/format/ruff.py +516 -0
  21. crackerjack/adapters/lint/README.md +47 -0
  22. crackerjack/adapters/lint/__init__.py +11 -0
  23. crackerjack/adapters/lint/codespell.py +273 -0
  24. crackerjack/adapters/lsp/README.md +49 -0
  25. crackerjack/adapters/lsp/__init__.py +27 -0
  26. crackerjack/adapters/lsp/_base.py +194 -0
  27. crackerjack/adapters/lsp/_client.py +358 -0
  28. crackerjack/adapters/lsp/_manager.py +193 -0
  29. crackerjack/adapters/lsp/skylos.py +283 -0
  30. crackerjack/adapters/lsp/zuban.py +557 -0
  31. crackerjack/adapters/refactor/README.md +59 -0
  32. crackerjack/adapters/refactor/__init__.py +12 -0
  33. crackerjack/adapters/refactor/creosote.py +318 -0
  34. crackerjack/adapters/refactor/refurb.py +406 -0
  35. crackerjack/adapters/refactor/skylos.py +494 -0
  36. crackerjack/adapters/sast/README.md +132 -0
  37. crackerjack/adapters/sast/__init__.py +32 -0
  38. crackerjack/adapters/sast/_base.py +201 -0
  39. crackerjack/adapters/sast/bandit.py +423 -0
  40. crackerjack/adapters/sast/pyscn.py +405 -0
  41. crackerjack/adapters/sast/semgrep.py +241 -0
  42. crackerjack/adapters/security/README.md +111 -0
  43. crackerjack/adapters/security/__init__.py +17 -0
  44. crackerjack/adapters/security/gitleaks.py +339 -0
  45. crackerjack/adapters/type/README.md +52 -0
  46. crackerjack/adapters/type/__init__.py +12 -0
  47. crackerjack/adapters/type/pyrefly.py +402 -0
  48. crackerjack/adapters/type/ty.py +402 -0
  49. crackerjack/adapters/type/zuban.py +522 -0
  50. crackerjack/adapters/utility/README.md +51 -0
  51. crackerjack/adapters/utility/__init__.py +10 -0
  52. crackerjack/adapters/utility/checks.py +884 -0
  53. crackerjack/agents/README.md +264 -0
  54. crackerjack/agents/__init__.py +66 -0
  55. crackerjack/agents/architect_agent.py +238 -0
  56. crackerjack/agents/base.py +167 -0
  57. crackerjack/agents/claude_code_bridge.py +641 -0
  58. crackerjack/agents/coordinator.py +600 -0
  59. crackerjack/agents/documentation_agent.py +520 -0
  60. crackerjack/agents/dry_agent.py +585 -0
  61. crackerjack/agents/enhanced_coordinator.py +279 -0
  62. crackerjack/agents/enhanced_proactive_agent.py +185 -0
  63. crackerjack/agents/error_middleware.py +53 -0
  64. crackerjack/agents/formatting_agent.py +230 -0
  65. crackerjack/agents/helpers/__init__.py +9 -0
  66. crackerjack/agents/helpers/performance/__init__.py +22 -0
  67. crackerjack/agents/helpers/performance/performance_ast_analyzer.py +357 -0
  68. crackerjack/agents/helpers/performance/performance_pattern_detector.py +909 -0
  69. crackerjack/agents/helpers/performance/performance_recommender.py +572 -0
  70. crackerjack/agents/helpers/refactoring/__init__.py +22 -0
  71. crackerjack/agents/helpers/refactoring/code_transformer.py +536 -0
  72. crackerjack/agents/helpers/refactoring/complexity_analyzer.py +344 -0
  73. crackerjack/agents/helpers/refactoring/dead_code_detector.py +437 -0
  74. crackerjack/agents/helpers/test_creation/__init__.py +19 -0
  75. crackerjack/agents/helpers/test_creation/test_ast_analyzer.py +216 -0
  76. crackerjack/agents/helpers/test_creation/test_coverage_analyzer.py +643 -0
  77. crackerjack/agents/helpers/test_creation/test_template_generator.py +1031 -0
  78. crackerjack/agents/import_optimization_agent.py +1181 -0
  79. crackerjack/agents/performance_agent.py +325 -0
  80. crackerjack/agents/performance_helpers.py +205 -0
  81. crackerjack/agents/proactive_agent.py +55 -0
  82. crackerjack/agents/refactoring_agent.py +511 -0
  83. crackerjack/agents/refactoring_helpers.py +247 -0
  84. crackerjack/agents/security_agent.py +793 -0
  85. crackerjack/agents/semantic_agent.py +479 -0
  86. crackerjack/agents/semantic_helpers.py +356 -0
  87. crackerjack/agents/test_creation_agent.py +570 -0
  88. crackerjack/agents/test_specialist_agent.py +526 -0
  89. crackerjack/agents/tracker.py +110 -0
  90. crackerjack/api.py +647 -0
  91. crackerjack/cli/README.md +394 -0
  92. crackerjack/cli/__init__.py +24 -0
  93. crackerjack/cli/cache_handlers.py +209 -0
  94. crackerjack/cli/cache_handlers_enhanced.py +680 -0
  95. crackerjack/cli/facade.py +162 -0
  96. crackerjack/cli/formatting.py +13 -0
  97. crackerjack/cli/handlers/__init__.py +85 -0
  98. crackerjack/cli/handlers/advanced.py +103 -0
  99. crackerjack/cli/handlers/ai_features.py +62 -0
  100. crackerjack/cli/handlers/analytics.py +479 -0
  101. crackerjack/cli/handlers/changelog.py +271 -0
  102. crackerjack/cli/handlers/config_handlers.py +16 -0
  103. crackerjack/cli/handlers/coverage.py +84 -0
  104. crackerjack/cli/handlers/documentation.py +280 -0
  105. crackerjack/cli/handlers/main_handlers.py +497 -0
  106. crackerjack/cli/handlers/monitoring.py +371 -0
  107. crackerjack/cli/handlers.py +700 -0
  108. crackerjack/cli/interactive.py +488 -0
  109. crackerjack/cli/options.py +1216 -0
  110. crackerjack/cli/semantic_handlers.py +292 -0
  111. crackerjack/cli/utils.py +19 -0
  112. crackerjack/cli/version.py +19 -0
  113. crackerjack/code_cleaner.py +1307 -0
  114. crackerjack/config/README.md +472 -0
  115. crackerjack/config/__init__.py +275 -0
  116. crackerjack/config/global_lock_config.py +207 -0
  117. crackerjack/config/hooks.py +390 -0
  118. crackerjack/config/loader.py +239 -0
  119. crackerjack/config/settings.py +141 -0
  120. crackerjack/config/tool_commands.py +331 -0
  121. crackerjack/core/README.md +393 -0
  122. crackerjack/core/__init__.py +0 -0
  123. crackerjack/core/async_workflow_orchestrator.py +738 -0
  124. crackerjack/core/autofix_coordinator.py +282 -0
  125. crackerjack/core/container.py +105 -0
  126. crackerjack/core/enhanced_container.py +583 -0
  127. crackerjack/core/file_lifecycle.py +472 -0
  128. crackerjack/core/performance.py +244 -0
  129. crackerjack/core/performance_monitor.py +357 -0
  130. crackerjack/core/phase_coordinator.py +1227 -0
  131. crackerjack/core/proactive_workflow.py +267 -0
  132. crackerjack/core/resource_manager.py +425 -0
  133. crackerjack/core/retry.py +275 -0
  134. crackerjack/core/service_watchdog.py +601 -0
  135. crackerjack/core/session_coordinator.py +239 -0
  136. crackerjack/core/timeout_manager.py +563 -0
  137. crackerjack/core/websocket_lifecycle.py +410 -0
  138. crackerjack/core/workflow/__init__.py +21 -0
  139. crackerjack/core/workflow/workflow_ai_coordinator.py +863 -0
  140. crackerjack/core/workflow/workflow_event_orchestrator.py +1107 -0
  141. crackerjack/core/workflow/workflow_issue_parser.py +714 -0
  142. crackerjack/core/workflow/workflow_phase_executor.py +1158 -0
  143. crackerjack/core/workflow/workflow_security_gates.py +400 -0
  144. crackerjack/core/workflow_orchestrator.py +2243 -0
  145. crackerjack/data/README.md +11 -0
  146. crackerjack/data/__init__.py +8 -0
  147. crackerjack/data/models.py +79 -0
  148. crackerjack/data/repository.py +210 -0
  149. crackerjack/decorators/README.md +180 -0
  150. crackerjack/decorators/__init__.py +35 -0
  151. crackerjack/decorators/error_handling.py +649 -0
  152. crackerjack/decorators/error_handling_decorators.py +334 -0
  153. crackerjack/decorators/helpers.py +58 -0
  154. crackerjack/decorators/patterns.py +281 -0
  155. crackerjack/decorators/utils.py +58 -0
  156. crackerjack/docs/INDEX.md +11 -0
  157. crackerjack/docs/README.md +11 -0
  158. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  159. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  160. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  161. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  162. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  163. crackerjack/documentation/README.md +11 -0
  164. crackerjack/documentation/__init__.py +31 -0
  165. crackerjack/documentation/ai_templates.py +756 -0
  166. crackerjack/documentation/dual_output_generator.py +767 -0
  167. crackerjack/documentation/mkdocs_integration.py +518 -0
  168. crackerjack/documentation/reference_generator.py +1065 -0
  169. crackerjack/dynamic_config.py +678 -0
  170. crackerjack/errors.py +378 -0
  171. crackerjack/events/README.md +11 -0
  172. crackerjack/events/__init__.py +16 -0
  173. crackerjack/events/telemetry.py +175 -0
  174. crackerjack/events/workflow_bus.py +346 -0
  175. crackerjack/exceptions/README.md +301 -0
  176. crackerjack/exceptions/__init__.py +5 -0
  177. crackerjack/exceptions/config.py +4 -0
  178. crackerjack/exceptions/tool_execution_error.py +245 -0
  179. crackerjack/executors/README.md +591 -0
  180. crackerjack/executors/__init__.py +13 -0
  181. crackerjack/executors/async_hook_executor.py +938 -0
  182. crackerjack/executors/cached_hook_executor.py +316 -0
  183. crackerjack/executors/hook_executor.py +1295 -0
  184. crackerjack/executors/hook_lock_manager.py +708 -0
  185. crackerjack/executors/individual_hook_executor.py +739 -0
  186. crackerjack/executors/lsp_aware_hook_executor.py +349 -0
  187. crackerjack/executors/progress_hook_executor.py +282 -0
  188. crackerjack/executors/tool_proxy.py +433 -0
  189. crackerjack/hooks/README.md +485 -0
  190. crackerjack/hooks/lsp_hook.py +93 -0
  191. crackerjack/intelligence/README.md +557 -0
  192. crackerjack/intelligence/__init__.py +37 -0
  193. crackerjack/intelligence/adaptive_learning.py +693 -0
  194. crackerjack/intelligence/agent_orchestrator.py +485 -0
  195. crackerjack/intelligence/agent_registry.py +377 -0
  196. crackerjack/intelligence/agent_selector.py +439 -0
  197. crackerjack/intelligence/integration.py +250 -0
  198. crackerjack/interactive.py +719 -0
  199. crackerjack/managers/README.md +369 -0
  200. crackerjack/managers/__init__.py +11 -0
  201. crackerjack/managers/async_hook_manager.py +135 -0
  202. crackerjack/managers/hook_manager.py +585 -0
  203. crackerjack/managers/publish_manager.py +631 -0
  204. crackerjack/managers/test_command_builder.py +391 -0
  205. crackerjack/managers/test_executor.py +474 -0
  206. crackerjack/managers/test_manager.py +1357 -0
  207. crackerjack/managers/test_progress.py +187 -0
  208. crackerjack/mcp/README.md +374 -0
  209. crackerjack/mcp/__init__.py +0 -0
  210. crackerjack/mcp/cache.py +352 -0
  211. crackerjack/mcp/client_runner.py +121 -0
  212. crackerjack/mcp/context.py +802 -0
  213. crackerjack/mcp/dashboard.py +657 -0
  214. crackerjack/mcp/enhanced_progress_monitor.py +493 -0
  215. crackerjack/mcp/file_monitor.py +394 -0
  216. crackerjack/mcp/progress_components.py +607 -0
  217. crackerjack/mcp/progress_monitor.py +1016 -0
  218. crackerjack/mcp/rate_limiter.py +336 -0
  219. crackerjack/mcp/server.py +24 -0
  220. crackerjack/mcp/server_core.py +526 -0
  221. crackerjack/mcp/service_watchdog.py +505 -0
  222. crackerjack/mcp/state.py +407 -0
  223. crackerjack/mcp/task_manager.py +259 -0
  224. crackerjack/mcp/tools/README.md +27 -0
  225. crackerjack/mcp/tools/__init__.py +19 -0
  226. crackerjack/mcp/tools/core_tools.py +469 -0
  227. crackerjack/mcp/tools/error_analyzer.py +283 -0
  228. crackerjack/mcp/tools/execution_tools.py +384 -0
  229. crackerjack/mcp/tools/intelligence_tool_registry.py +46 -0
  230. crackerjack/mcp/tools/intelligence_tools.py +264 -0
  231. crackerjack/mcp/tools/monitoring_tools.py +628 -0
  232. crackerjack/mcp/tools/proactive_tools.py +367 -0
  233. crackerjack/mcp/tools/progress_tools.py +222 -0
  234. crackerjack/mcp/tools/semantic_tools.py +584 -0
  235. crackerjack/mcp/tools/utility_tools.py +358 -0
  236. crackerjack/mcp/tools/workflow_executor.py +699 -0
  237. crackerjack/mcp/websocket/README.md +31 -0
  238. crackerjack/mcp/websocket/__init__.py +14 -0
  239. crackerjack/mcp/websocket/app.py +54 -0
  240. crackerjack/mcp/websocket/endpoints.py +492 -0
  241. crackerjack/mcp/websocket/event_bridge.py +188 -0
  242. crackerjack/mcp/websocket/jobs.py +406 -0
  243. crackerjack/mcp/websocket/monitoring/__init__.py +25 -0
  244. crackerjack/mcp/websocket/monitoring/api/__init__.py +19 -0
  245. crackerjack/mcp/websocket/monitoring/api/dependencies.py +141 -0
  246. crackerjack/mcp/websocket/monitoring/api/heatmap.py +154 -0
  247. crackerjack/mcp/websocket/monitoring/api/intelligence.py +199 -0
  248. crackerjack/mcp/websocket/monitoring/api/metrics.py +203 -0
  249. crackerjack/mcp/websocket/monitoring/api/telemetry.py +101 -0
  250. crackerjack/mcp/websocket/monitoring/dashboard.py +18 -0
  251. crackerjack/mcp/websocket/monitoring/factory.py +109 -0
  252. crackerjack/mcp/websocket/monitoring/filters.py +10 -0
  253. crackerjack/mcp/websocket/monitoring/metrics.py +64 -0
  254. crackerjack/mcp/websocket/monitoring/models.py +90 -0
  255. crackerjack/mcp/websocket/monitoring/utils.py +171 -0
  256. crackerjack/mcp/websocket/monitoring/websocket_manager.py +78 -0
  257. crackerjack/mcp/websocket/monitoring/websockets/__init__.py +17 -0
  258. crackerjack/mcp/websocket/monitoring/websockets/dependencies.py +126 -0
  259. crackerjack/mcp/websocket/monitoring/websockets/heatmap.py +176 -0
  260. crackerjack/mcp/websocket/monitoring/websockets/intelligence.py +291 -0
  261. crackerjack/mcp/websocket/monitoring/websockets/metrics.py +291 -0
  262. crackerjack/mcp/websocket/monitoring_endpoints.py +21 -0
  263. crackerjack/mcp/websocket/server.py +174 -0
  264. crackerjack/mcp/websocket/websocket_handler.py +276 -0
  265. crackerjack/mcp/websocket_server.py +10 -0
  266. crackerjack/models/README.md +308 -0
  267. crackerjack/models/__init__.py +40 -0
  268. crackerjack/models/config.py +730 -0
  269. crackerjack/models/config_adapter.py +265 -0
  270. crackerjack/models/protocols.py +1535 -0
  271. crackerjack/models/pydantic_models.py +320 -0
  272. crackerjack/models/qa_config.py +145 -0
  273. crackerjack/models/qa_results.py +134 -0
  274. crackerjack/models/resource_protocols.py +299 -0
  275. crackerjack/models/results.py +35 -0
  276. crackerjack/models/semantic_models.py +258 -0
  277. crackerjack/models/task.py +173 -0
  278. crackerjack/models/test_models.py +60 -0
  279. crackerjack/monitoring/README.md +11 -0
  280. crackerjack/monitoring/__init__.py +0 -0
  281. crackerjack/monitoring/ai_agent_watchdog.py +405 -0
  282. crackerjack/monitoring/metrics_collector.py +427 -0
  283. crackerjack/monitoring/regression_prevention.py +580 -0
  284. crackerjack/monitoring/websocket_server.py +406 -0
  285. crackerjack/orchestration/README.md +340 -0
  286. crackerjack/orchestration/__init__.py +43 -0
  287. crackerjack/orchestration/advanced_orchestrator.py +894 -0
  288. crackerjack/orchestration/cache/README.md +312 -0
  289. crackerjack/orchestration/cache/__init__.py +37 -0
  290. crackerjack/orchestration/cache/memory_cache.py +338 -0
  291. crackerjack/orchestration/cache/tool_proxy_cache.py +340 -0
  292. crackerjack/orchestration/config.py +297 -0
  293. crackerjack/orchestration/coverage_improvement.py +180 -0
  294. crackerjack/orchestration/execution_strategies.py +361 -0
  295. crackerjack/orchestration/hook_orchestrator.py +1398 -0
  296. crackerjack/orchestration/strategies/README.md +401 -0
  297. crackerjack/orchestration/strategies/__init__.py +39 -0
  298. crackerjack/orchestration/strategies/adaptive_strategy.py +630 -0
  299. crackerjack/orchestration/strategies/parallel_strategy.py +237 -0
  300. crackerjack/orchestration/strategies/sequential_strategy.py +299 -0
  301. crackerjack/orchestration/test_progress_streamer.py +647 -0
  302. crackerjack/plugins/README.md +11 -0
  303. crackerjack/plugins/__init__.py +15 -0
  304. crackerjack/plugins/base.py +200 -0
  305. crackerjack/plugins/hooks.py +254 -0
  306. crackerjack/plugins/loader.py +335 -0
  307. crackerjack/plugins/managers.py +264 -0
  308. crackerjack/py313.py +191 -0
  309. crackerjack/security/README.md +11 -0
  310. crackerjack/security/__init__.py +0 -0
  311. crackerjack/security/audit.py +197 -0
  312. crackerjack/services/README.md +374 -0
  313. crackerjack/services/__init__.py +9 -0
  314. crackerjack/services/ai/README.md +295 -0
  315. crackerjack/services/ai/__init__.py +7 -0
  316. crackerjack/services/ai/advanced_optimizer.py +878 -0
  317. crackerjack/services/ai/contextual_ai_assistant.py +542 -0
  318. crackerjack/services/ai/embeddings.py +444 -0
  319. crackerjack/services/ai/intelligent_commit.py +328 -0
  320. crackerjack/services/ai/predictive_analytics.py +510 -0
  321. crackerjack/services/anomaly_detector.py +392 -0
  322. crackerjack/services/api_extractor.py +617 -0
  323. crackerjack/services/backup_service.py +467 -0
  324. crackerjack/services/bounded_status_operations.py +530 -0
  325. crackerjack/services/cache.py +369 -0
  326. crackerjack/services/changelog_automation.py +399 -0
  327. crackerjack/services/command_execution_service.py +305 -0
  328. crackerjack/services/config_integrity.py +132 -0
  329. crackerjack/services/config_merge.py +546 -0
  330. crackerjack/services/config_service.py +198 -0
  331. crackerjack/services/config_template.py +493 -0
  332. crackerjack/services/coverage_badge_service.py +173 -0
  333. crackerjack/services/coverage_ratchet.py +381 -0
  334. crackerjack/services/debug.py +733 -0
  335. crackerjack/services/dependency_analyzer.py +460 -0
  336. crackerjack/services/dependency_monitor.py +622 -0
  337. crackerjack/services/documentation_generator.py +493 -0
  338. crackerjack/services/documentation_service.py +704 -0
  339. crackerjack/services/enhanced_filesystem.py +497 -0
  340. crackerjack/services/enterprise_optimizer.py +865 -0
  341. crackerjack/services/error_pattern_analyzer.py +676 -0
  342. crackerjack/services/file_filter.py +221 -0
  343. crackerjack/services/file_hasher.py +149 -0
  344. crackerjack/services/file_io_service.py +361 -0
  345. crackerjack/services/file_modifier.py +615 -0
  346. crackerjack/services/filesystem.py +381 -0
  347. crackerjack/services/git.py +422 -0
  348. crackerjack/services/health_metrics.py +615 -0
  349. crackerjack/services/heatmap_generator.py +744 -0
  350. crackerjack/services/incremental_executor.py +380 -0
  351. crackerjack/services/initialization.py +823 -0
  352. crackerjack/services/input_validator.py +668 -0
  353. crackerjack/services/intelligent_commit.py +327 -0
  354. crackerjack/services/log_manager.py +289 -0
  355. crackerjack/services/logging.py +228 -0
  356. crackerjack/services/lsp_client.py +628 -0
  357. crackerjack/services/memory_optimizer.py +414 -0
  358. crackerjack/services/metrics.py +587 -0
  359. crackerjack/services/monitoring/README.md +30 -0
  360. crackerjack/services/monitoring/__init__.py +9 -0
  361. crackerjack/services/monitoring/dependency_monitor.py +678 -0
  362. crackerjack/services/monitoring/error_pattern_analyzer.py +676 -0
  363. crackerjack/services/monitoring/health_metrics.py +716 -0
  364. crackerjack/services/monitoring/metrics.py +587 -0
  365. crackerjack/services/monitoring/performance_benchmarks.py +410 -0
  366. crackerjack/services/monitoring/performance_cache.py +388 -0
  367. crackerjack/services/monitoring/performance_monitor.py +569 -0
  368. crackerjack/services/parallel_executor.py +527 -0
  369. crackerjack/services/pattern_cache.py +333 -0
  370. crackerjack/services/pattern_detector.py +478 -0
  371. crackerjack/services/patterns/__init__.py +142 -0
  372. crackerjack/services/patterns/agents.py +107 -0
  373. crackerjack/services/patterns/code/__init__.py +15 -0
  374. crackerjack/services/patterns/code/detection.py +118 -0
  375. crackerjack/services/patterns/code/imports.py +107 -0
  376. crackerjack/services/patterns/code/paths.py +159 -0
  377. crackerjack/services/patterns/code/performance.py +119 -0
  378. crackerjack/services/patterns/code/replacement.py +36 -0
  379. crackerjack/services/patterns/core.py +212 -0
  380. crackerjack/services/patterns/documentation/__init__.py +14 -0
  381. crackerjack/services/patterns/documentation/badges_markdown.py +96 -0
  382. crackerjack/services/patterns/documentation/comments_blocks.py +83 -0
  383. crackerjack/services/patterns/documentation/docstrings.py +89 -0
  384. crackerjack/services/patterns/formatting.py +226 -0
  385. crackerjack/services/patterns/operations.py +339 -0
  386. crackerjack/services/patterns/security/__init__.py +23 -0
  387. crackerjack/services/patterns/security/code_injection.py +122 -0
  388. crackerjack/services/patterns/security/credentials.py +190 -0
  389. crackerjack/services/patterns/security/path_traversal.py +221 -0
  390. crackerjack/services/patterns/security/unsafe_operations.py +216 -0
  391. crackerjack/services/patterns/templates.py +62 -0
  392. crackerjack/services/patterns/testing/__init__.py +18 -0
  393. crackerjack/services/patterns/testing/error_patterns.py +107 -0
  394. crackerjack/services/patterns/testing/pytest_output.py +126 -0
  395. crackerjack/services/patterns/tool_output/__init__.py +16 -0
  396. crackerjack/services/patterns/tool_output/bandit.py +72 -0
  397. crackerjack/services/patterns/tool_output/other.py +97 -0
  398. crackerjack/services/patterns/tool_output/pyright.py +67 -0
  399. crackerjack/services/patterns/tool_output/ruff.py +44 -0
  400. crackerjack/services/patterns/url_sanitization.py +114 -0
  401. crackerjack/services/patterns/utilities.py +42 -0
  402. crackerjack/services/patterns/utils.py +339 -0
  403. crackerjack/services/patterns/validation.py +46 -0
  404. crackerjack/services/patterns/versioning.py +62 -0
  405. crackerjack/services/predictive_analytics.py +523 -0
  406. crackerjack/services/profiler.py +280 -0
  407. crackerjack/services/quality/README.md +415 -0
  408. crackerjack/services/quality/__init__.py +11 -0
  409. crackerjack/services/quality/anomaly_detector.py +392 -0
  410. crackerjack/services/quality/pattern_cache.py +333 -0
  411. crackerjack/services/quality/pattern_detector.py +479 -0
  412. crackerjack/services/quality/qa_orchestrator.py +491 -0
  413. crackerjack/services/quality/quality_baseline.py +395 -0
  414. crackerjack/services/quality/quality_baseline_enhanced.py +649 -0
  415. crackerjack/services/quality/quality_intelligence.py +949 -0
  416. crackerjack/services/regex_patterns.py +58 -0
  417. crackerjack/services/regex_utils.py +483 -0
  418. crackerjack/services/secure_path_utils.py +524 -0
  419. crackerjack/services/secure_status_formatter.py +450 -0
  420. crackerjack/services/secure_subprocess.py +635 -0
  421. crackerjack/services/security.py +239 -0
  422. crackerjack/services/security_logger.py +495 -0
  423. crackerjack/services/server_manager.py +411 -0
  424. crackerjack/services/smart_scheduling.py +167 -0
  425. crackerjack/services/status_authentication.py +460 -0
  426. crackerjack/services/status_security_manager.py +315 -0
  427. crackerjack/services/terminal_utils.py +0 -0
  428. crackerjack/services/thread_safe_status_collector.py +441 -0
  429. crackerjack/services/tool_filter.py +368 -0
  430. crackerjack/services/tool_version_service.py +43 -0
  431. crackerjack/services/unified_config.py +115 -0
  432. crackerjack/services/validation_rate_limiter.py +220 -0
  433. crackerjack/services/vector_store.py +689 -0
  434. crackerjack/services/version_analyzer.py +461 -0
  435. crackerjack/services/version_checker.py +223 -0
  436. crackerjack/services/websocket_resource_limiter.py +438 -0
  437. crackerjack/services/zuban_lsp_service.py +391 -0
  438. crackerjack/slash_commands/README.md +11 -0
  439. crackerjack/slash_commands/__init__.py +59 -0
  440. crackerjack/slash_commands/init.md +112 -0
  441. crackerjack/slash_commands/run.md +197 -0
  442. crackerjack/slash_commands/status.md +127 -0
  443. crackerjack/tools/README.md +11 -0
  444. crackerjack/tools/__init__.py +30 -0
  445. crackerjack/tools/_git_utils.py +105 -0
  446. crackerjack/tools/check_added_large_files.py +139 -0
  447. crackerjack/tools/check_ast.py +105 -0
  448. crackerjack/tools/check_json.py +103 -0
  449. crackerjack/tools/check_jsonschema.py +297 -0
  450. crackerjack/tools/check_toml.py +103 -0
  451. crackerjack/tools/check_yaml.py +110 -0
  452. crackerjack/tools/codespell_wrapper.py +72 -0
  453. crackerjack/tools/end_of_file_fixer.py +202 -0
  454. crackerjack/tools/format_json.py +128 -0
  455. crackerjack/tools/mdformat_wrapper.py +114 -0
  456. crackerjack/tools/trailing_whitespace.py +198 -0
  457. crackerjack/tools/validate_input_validator_patterns.py +236 -0
  458. crackerjack/tools/validate_regex_patterns.py +188 -0
  459. crackerjack/ui/README.md +11 -0
  460. crackerjack/ui/__init__.py +1 -0
  461. crackerjack/ui/dashboard_renderer.py +28 -0
  462. crackerjack/ui/templates/README.md +11 -0
  463. crackerjack/utils/console_utils.py +13 -0
  464. crackerjack/utils/dependency_guard.py +230 -0
  465. crackerjack/utils/retry_utils.py +275 -0
  466. crackerjack/workflows/README.md +590 -0
  467. crackerjack/workflows/__init__.py +46 -0
  468. crackerjack/workflows/actions.py +811 -0
  469. crackerjack/workflows/auto_fix.py +444 -0
  470. crackerjack/workflows/container_builder.py +499 -0
  471. crackerjack/workflows/definitions.py +443 -0
  472. crackerjack/workflows/engine.py +177 -0
  473. crackerjack/workflows/event_bridge.py +242 -0
  474. crackerjack-0.45.2.dist-info/METADATA +1678 -0
  475. crackerjack-0.45.2.dist-info/RECORD +478 -0
  476. {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
  477. crackerjack-0.45.2.dist-info/entry_points.txt +2 -0
  478. crackerjack/.gitignore +0 -14
  479. crackerjack/.libcst.codemod.yaml +0 -18
  480. crackerjack/.pdm.toml +0 -1
  481. crackerjack/.pre-commit-config.yaml +0 -91
  482. crackerjack/.pytest_cache/.gitignore +0 -2
  483. crackerjack/.pytest_cache/CACHEDIR.TAG +0 -4
  484. crackerjack/.pytest_cache/README.md +0 -8
  485. crackerjack/.pytest_cache/v/cache/nodeids +0 -1
  486. crackerjack/.pytest_cache/v/cache/stepwise +0 -1
  487. crackerjack/.ruff_cache/.gitignore +0 -1
  488. crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
  489. crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
  490. crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
  491. crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
  492. crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
  493. crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
  494. crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
  495. crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
  496. crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
  497. crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
  498. crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
  499. crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
  500. crackerjack/.ruff_cache/0.11.8/530407680854991027 +0 -0
  501. crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
  502. crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
  503. crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
  504. crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
  505. crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
  506. crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
  507. crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
  508. crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
  509. crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
  510. crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
  511. crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
  512. crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
  513. crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
  514. crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
  515. crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
  516. crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
  517. crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
  518. crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
  519. crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
  520. crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
  521. crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
  522. crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
  523. crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
  524. crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
  525. crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
  526. crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
  527. crackerjack/.ruff_cache/CACHEDIR.TAG +0 -1
  528. crackerjack/crackerjack.py +0 -855
  529. crackerjack/pyproject.toml +0 -214
  530. crackerjack-0.18.2.dist-info/METADATA +0 -420
  531. crackerjack-0.18.2.dist-info/RECORD +0 -59
  532. crackerjack-0.18.2.dist-info/entry_points.txt +0 -4
  533. {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,635 @@
1
+ import os
2
+ import re
3
+ import subprocess
4
+ import time
5
+ import typing as t
6
+ from pathlib import Path
7
+
8
+ from .security_logger import SecurityEventLevel, SecurityEventType, get_security_logger
9
+
10
+
11
+ class SecurityError(Exception):
12
+ pass
13
+
14
+
15
+ class CommandValidationError(SecurityError):
16
+ pass
17
+
18
+
19
+ class EnvironmentValidationError(SecurityError):
20
+ pass
21
+
22
+
23
+ class SubprocessSecurityConfig:
24
+ def __init__(
25
+ self,
26
+ max_command_length: int = 10000,
27
+ max_arg_length: int = 4096,
28
+ max_env_var_length: int = 32768,
29
+ max_env_vars: int = 1000,
30
+ allowed_executables: set[str] | None = None,
31
+ blocked_executables: set[str] | None = None,
32
+ max_timeout: float = 3600,
33
+ enable_path_validation: bool = True,
34
+ enable_command_logging: bool = True,
35
+ ):
36
+ self.max_command_length = max_command_length
37
+ self.max_arg_length = max_arg_length
38
+ self.max_env_var_length = max_env_var_length
39
+ self.max_env_vars = max_env_vars
40
+ self.allowed_executables = allowed_executables or set()
41
+ self.blocked_executables = blocked_executables or {
42
+ "rm",
43
+ "rmdir",
44
+ "del",
45
+ "format",
46
+ "fdisk",
47
+ "mkfs",
48
+ "dd",
49
+ "shred",
50
+ "wipe",
51
+ "nc",
52
+ "netcat",
53
+ "telnet",
54
+ "ftp",
55
+ "tftp",
56
+ "curl",
57
+ "wget",
58
+ "ssh",
59
+ "scp",
60
+ "rsync",
61
+ "sudo",
62
+ "su",
63
+ "doas",
64
+ "eval",
65
+ "exec",
66
+ "source",
67
+ ".",
68
+ "bash",
69
+ "sh",
70
+ "zsh",
71
+ "fish",
72
+ "csh",
73
+ }
74
+ self.max_timeout = max_timeout
75
+ self.enable_path_validation = enable_path_validation
76
+ self.enable_command_logging = enable_command_logging
77
+
78
+
79
+ class SecureSubprocessExecutor:
80
+ def __init__(self, config: SubprocessSecurityConfig | None = None):
81
+ if config is None:
82
+ # When no explicit config provided, respect debug mode for logging
83
+ debug_enabled = os.environ.get("CRACKERJACK_DEBUG", "0") == "1"
84
+ self.config = SubprocessSecurityConfig(enable_command_logging=debug_enabled)
85
+ else:
86
+ self.config = config
87
+ self.security_logger = get_security_logger()
88
+
89
+ self.dangerous_patterns = [
90
+ r"[;&|`$()[\]<>*?~]",
91
+ r"\.\./",
92
+ r"\$\{.*\}",
93
+ r"`.*`",
94
+ r"\$\(.*\)",
95
+ r">\s*/",
96
+ r"<\s*/",
97
+ ]
98
+
99
+ # Git reference patterns that should be allowed despite containing special chars
100
+ self.allowed_git_patterns = [
101
+ r"^@\{u\}\.\.HEAD$", # upstream..HEAD
102
+ r"^@\{upstream\}\.\.HEAD$", # upstream..HEAD (long form)
103
+ r"^HEAD\.\.@\{u\}$", # HEAD..upstream
104
+ r"^HEAD\.\.@\{upstream\}$", # HEAD..upstream (long form)
105
+ r"^@\{[0-9]+\}$", # reflog references like @{1}
106
+ r"^@\{[0-9]+ (minute|hour|day|week|month|year)s? ago\}$", # time references
107
+ ]
108
+
109
+ self.dangerous_env_vars = {
110
+ "LD_PRELOAD",
111
+ "DYLD_INSERT_LIBRARIES",
112
+ "DYLD_LIBRARY_PATH",
113
+ "LD_LIBRARY_PATH",
114
+ "PYTHONPATH",
115
+ "PATH",
116
+ "IFS",
117
+ "PS4",
118
+ "BASH_ENV",
119
+ "ENV",
120
+ "SHELLOPTS",
121
+ "BASHOPTS",
122
+ }
123
+
124
+ self.safe_env_vars = {
125
+ "HOME",
126
+ "USER",
127
+ "USERNAME",
128
+ "LOGNAME",
129
+ "LANG",
130
+ "LC_ALL",
131
+ "LC_CTYPE",
132
+ "TERM",
133
+ "TMPDIR",
134
+ "TMP",
135
+ "TEMP",
136
+ }
137
+
138
+ def execute_secure(
139
+ self,
140
+ command: list[str],
141
+ cwd: Path | str | None = None,
142
+ env: dict[str, str] | None = None,
143
+ timeout: float | None = None,
144
+ input_data: str | bytes | None = None,
145
+ capture_output: bool = True,
146
+ text: bool = True,
147
+ check: bool = False,
148
+ **kwargs: t.Any,
149
+ ) -> subprocess.CompletedProcess[str]:
150
+ start_time = time.time()
151
+
152
+ try:
153
+ return self._execute_with_validation(
154
+ command,
155
+ cwd,
156
+ env,
157
+ timeout,
158
+ input_data,
159
+ capture_output,
160
+ text,
161
+ check,
162
+ kwargs,
163
+ start_time,
164
+ )
165
+
166
+ except subprocess.TimeoutExpired:
167
+ self._handle_timeout_error(command, timeout, start_time)
168
+ raise
169
+
170
+ except subprocess.CalledProcessError as e:
171
+ self._handle_process_error(command, e)
172
+ raise
173
+
174
+ except Exception as e:
175
+ self._handle_unexpected_error(command, e)
176
+ raise
177
+
178
+ def _execute_with_validation(
179
+ self,
180
+ command: list[str],
181
+ cwd: Path | str | None,
182
+ env: dict[str, str] | None,
183
+ timeout: float | None,
184
+ input_data: str | bytes | None,
185
+ capture_output: bool,
186
+ text: bool,
187
+ check: bool,
188
+ kwargs: dict[str, t.Any],
189
+ start_time: float,
190
+ ) -> subprocess.CompletedProcess[str]:
191
+ execution_params = self._prepare_execution_params(command, cwd, env, timeout)
192
+
193
+ result = self._execute_subprocess(
194
+ execution_params, input_data, capture_output, text, check, kwargs
195
+ )
196
+
197
+ self._log_successful_execution(execution_params, result, start_time)
198
+ return result
199
+
200
+ def _prepare_execution_params(
201
+ self,
202
+ command: list[str],
203
+ cwd: Path | str | None,
204
+ env: dict[str, str] | None,
205
+ timeout: float | None,
206
+ ) -> dict[str, t.Any]:
207
+ return {
208
+ "command": self._validate_command(command),
209
+ "cwd": self._validate_cwd(cwd),
210
+ "env": self._sanitize_environment(env),
211
+ "timeout": self._validate_timeout(timeout),
212
+ }
213
+
214
+ def _execute_subprocess(
215
+ self,
216
+ params: dict[str, t.Any],
217
+ input_data: str | bytes | None,
218
+ capture_output: bool,
219
+ text: bool,
220
+ check: bool,
221
+ kwargs: dict[str, t.Any],
222
+ ) -> subprocess.CompletedProcess[str]:
223
+ if self.config.enable_command_logging:
224
+ self.security_logger.log_subprocess_execution(
225
+ command=params["command"],
226
+ cwd=str(params["cwd"]) if params["cwd"] else None,
227
+ env_vars_count=len(params["env"]),
228
+ timeout=params["timeout"],
229
+ )
230
+
231
+ return subprocess.run(
232
+ params["command"],
233
+ cwd=params["cwd"],
234
+ env=params["env"],
235
+ timeout=params["timeout"],
236
+ input=input_data,
237
+ capture_output=capture_output,
238
+ text=text,
239
+ check=check,
240
+ **kwargs,
241
+ )
242
+
243
+ def _log_successful_execution(
244
+ self,
245
+ params: dict[str, t.Any],
246
+ result: subprocess.CompletedProcess[str],
247
+ start_time: float,
248
+ ) -> None:
249
+ execution_time = time.time() - start_time
250
+ if self.config.enable_command_logging:
251
+ self.security_logger.log_security_event(
252
+ SecurityEventType.SUBPROCESS_EXECUTION,
253
+ SecurityEventLevel.LOW,
254
+ f"Subprocess completed successfully in {execution_time: .2f}s",
255
+ command_preview=params["command"][:3],
256
+ execution_time=execution_time,
257
+ exit_code=result.returncode,
258
+ )
259
+
260
+ def _handle_timeout_error(
261
+ self, command: list[str], timeout: float | None, start_time: float
262
+ ) -> None:
263
+ execution_time = time.time() - start_time
264
+ self.security_logger.log_subprocess_timeout(
265
+ command=command,
266
+ timeout_seconds=timeout or self.config.max_timeout,
267
+ actual_duration=execution_time,
268
+ )
269
+
270
+ def _handle_process_error(
271
+ self, command: list[str], error: subprocess.CalledProcessError
272
+ ) -> None:
273
+ self.security_logger.log_subprocess_failure(
274
+ command=command,
275
+ exit_code=error.returncode,
276
+ error_output=str(error.stderr)[:200] if error.stderr else "",
277
+ )
278
+
279
+ def _handle_unexpected_error(self, command: list[str], error: Exception) -> None:
280
+ self.security_logger.log_security_event(
281
+ SecurityEventType.SUBPROCESS_FAILURE,
282
+ SecurityEventLevel.HIGH,
283
+ f"Unexpected subprocess error: {str(error)[:200]}",
284
+ command_preview=command[:3] if command else [],
285
+ error_type=type(error).__name__,
286
+ error_message=str(error)[:200],
287
+ )
288
+
289
+ def _validate_command(self, command: list[str]) -> list[str]:
290
+ self._validate_command_structure(command)
291
+
292
+ validated_command, issues = self._validate_command_arguments(command)
293
+ self._validate_executable_permissions(validated_command, issues)
294
+
295
+ self._handle_validation_results(command, issues)
296
+ return validated_command
297
+
298
+ def _validate_command_structure(self, command: list[str]) -> None:
299
+ if not command:
300
+ raise CommandValidationError("Command cannot be empty")
301
+
302
+ total_length = sum(len(arg) for arg in command)
303
+ if total_length > self.config.max_command_length:
304
+ raise CommandValidationError(
305
+ f"Command too long: {total_length} > {self.config.max_command_length}"
306
+ )
307
+
308
+ def _validate_command_arguments(
309
+ self, command: list[str]
310
+ ) -> tuple[list[str], list[str]]:
311
+ validated_command = []
312
+ issues = []
313
+
314
+ for i, arg in enumerate(command):
315
+ if len(arg) > self.config.max_arg_length:
316
+ issues.append(
317
+ f"Argument {i} too long: {len(arg)} > {self.config.max_arg_length}"
318
+ )
319
+ continue
320
+
321
+ if self._has_dangerous_patterns(arg, i, issues, command):
322
+ continue
323
+
324
+ validated_command.append(arg)
325
+
326
+ return validated_command, issues
327
+
328
+ def _has_dangerous_patterns(
329
+ self, arg: str, index: int, issues: list[str], command: list[str]
330
+ ) -> bool:
331
+ # First check if this is an allowed git pattern
332
+ if self._is_allowed_git_pattern(arg):
333
+ return False
334
+
335
+ # Special handling for git commit messages
336
+ if self._is_git_commit_message(index, command):
337
+ return self._check_dangerous_patterns_in_commit_message(arg, index, issues)
338
+
339
+ # Check for dangerous patterns in other contexts
340
+ return self._check_dangerous_patterns_in_other_contexts(arg, index, issues)
341
+
342
+ def _is_allowed_git_pattern(self, arg: str) -> bool:
343
+ """Check if the argument matches an allowed git pattern."""
344
+ for git_pattern in self.allowed_git_patterns:
345
+ if re.match(git_pattern, arg):
346
+ return True
347
+ return False
348
+
349
+ def _check_dangerous_patterns_in_commit_message(
350
+ self, arg: str, index: int, issues: list[str]
351
+ ) -> bool:
352
+ """Check for dangerous patterns specifically in git commit messages."""
353
+ # For git commit messages, only check for truly dangerous patterns
354
+ # Parentheses are common in commit messages and should be allowed
355
+ safe_commit_patterns = [
356
+ r"[;&|`$]", # Still dangerous in commit messages
357
+ r"\.\./", # Path traversal
358
+ r"\$\{.*\}", # Variable expansion
359
+ r"`.*`", # Command substitution
360
+ r"\$\(.*\)", # Command substitution (but allow simple parentheses)
361
+ r">\s*/", # Redirection to paths
362
+ r"<\s*/", # Redirection from paths
363
+ ]
364
+
365
+ for pattern in safe_commit_patterns:
366
+ if re.search(pattern, arg):
367
+ # Allow simple parentheses that don't look like command substitution
368
+ if pattern == r"\$\(.*\)" and not re.search(r"\$\(", arg):
369
+ continue
370
+ issues.append(
371
+ f"Dangerous pattern '{pattern}' in argument {index}: {arg[:50]}"
372
+ )
373
+ return True
374
+ return False
375
+
376
+ def _check_dangerous_patterns_in_other_contexts(
377
+ self, arg: str, index: int, issues: list[str]
378
+ ) -> bool:
379
+ """Check for dangerous patterns in non-commit message contexts."""
380
+ for pattern in self.dangerous_patterns:
381
+ if re.search(pattern, arg):
382
+ issues.append(
383
+ f"Dangerous pattern '{pattern}' in argument {index}: {arg[:50]}"
384
+ )
385
+ return True
386
+ return False
387
+
388
+ def _is_git_commit_message(self, index: int, command: list[str]) -> bool:
389
+ """Check if the current argument is likely a git commit message."""
390
+ # Check if we have a git commit command structure: git commit -m <message>
391
+ if (
392
+ len(command) >= 3
393
+ and command[0] == "git"
394
+ and command[1] == "commit"
395
+ and command[2] == "-m"
396
+ ):
397
+ return index == 3
398
+ return False
399
+
400
+ def _validate_executable_permissions(
401
+ self, validated_command: list[str], issues: list[str]
402
+ ) -> None:
403
+ if not validated_command:
404
+ return
405
+
406
+ executable = Path(validated_command[0]).name
407
+
408
+ if (
409
+ self.config.allowed_executables
410
+ and executable not in self.config.allowed_executables
411
+ ):
412
+ issues.append(f"Executable '{executable}' not in allowlist")
413
+
414
+ if executable in self.config.blocked_executables:
415
+ issues.append(f"Executable '{executable}' is blocked")
416
+
417
+ def _handle_validation_results(self, command: list[str], issues: list[str]) -> None:
418
+ validation_passed = len(issues) == 0
419
+ if self.config.enable_command_logging:
420
+ self.security_logger.log_subprocess_command_validation(
421
+ command=command,
422
+ validation_result=validation_passed,
423
+ issues=issues,
424
+ )
425
+
426
+ if issues:
427
+ self.security_logger.log_dangerous_command_blocked(
428
+ command=command,
429
+ reason="Command validation failed",
430
+ dangerous_patterns=issues,
431
+ )
432
+ raise CommandValidationError(
433
+ f"Command validation failed: {'; '.join(issues)}"
434
+ )
435
+
436
+ def _validate_cwd(self, cwd: Path | str | None) -> Path | None:
437
+ if cwd is None:
438
+ return None
439
+
440
+ if not self.config.enable_path_validation:
441
+ return Path(cwd) if isinstance(cwd, str) else cwd
442
+
443
+ cwd_path = Path(cwd) if isinstance(cwd, str) else cwd
444
+
445
+ try:
446
+ resolved_path = cwd_path.resolve()
447
+
448
+ # Get the original path's components to check for path traversal patterns
449
+ original_path_obj = Path(cwd)
450
+ original_parts = original_path_obj.parts
451
+ if ".." in original_parts or any(
452
+ part.startswith("../") for part in original_parts
453
+ ):
454
+ # Check if the resolved path is within the safe base (project directory or temp)
455
+ safe_root = (
456
+ Path.cwd().parent.resolve()
457
+ ) # Use parent of project root as broader safe root
458
+ try:
459
+ resolved_path.relative_to(safe_root)
460
+ except ValueError:
461
+ # If relative_to raises ValueError, path is outside safe root
462
+ path_str = str(resolved_path)
463
+ self.security_logger.log_path_traversal_attempt(
464
+ attempted_path=path_str,
465
+ base_directory=str(safe_root),
466
+ )
467
+ raise CommandValidationError(
468
+ f"Dangerous working directory: {path_str}"
469
+ )
470
+
471
+ path_str = str(resolved_path)
472
+ # Check for system directories, including macOS /private variants
473
+ if path_str.startswith(
474
+ (
475
+ "/etc",
476
+ "/usr/bin",
477
+ "/bin",
478
+ "/sbin",
479
+ "/private/etc",
480
+ "/private/usr/bin",
481
+ "/private/bin",
482
+ "/private/sbin",
483
+ )
484
+ ):
485
+ self.security_logger.log_path_traversal_attempt(
486
+ attempted_path=path_str,
487
+ base_directory=None,
488
+ )
489
+ raise CommandValidationError(f"Dangerous working directory: {path_str}")
490
+
491
+ return resolved_path
492
+
493
+ except (OSError, ValueError) as e:
494
+ raise CommandValidationError(f"Invalid working directory '{cwd}': {e}")
495
+
496
+ def _sanitize_environment(self, env: dict[str, str] | None) -> dict[str, str]:
497
+ if env is None:
498
+ env = os.environ.copy()
499
+
500
+ self._validate_environment_size(env)
501
+
502
+ filtered_vars: list[str] = []
503
+ sanitized_env = self._filter_environment_variables(env, filtered_vars)
504
+
505
+ self._add_safe_environment_variables(sanitized_env)
506
+ self._log_environment_sanitization(len(env), len(sanitized_env), filtered_vars)
507
+
508
+ return sanitized_env
509
+
510
+ def _validate_environment_size(self, env: dict[str, str]) -> None:
511
+ if len(env) > self.config.max_env_vars:
512
+ self.security_logger.log_security_event(
513
+ SecurityEventType.INPUT_SIZE_EXCEEDED,
514
+ SecurityEventLevel.HIGH,
515
+ f"Too many environment variables: {len(env)} > {self.config.max_env_vars}",
516
+ actual_count=len(env),
517
+ max_count=self.config.max_env_vars,
518
+ )
519
+ raise EnvironmentValidationError(
520
+ f"Too many environment variables: {len(env)} > {self.config.max_env_vars}"
521
+ )
522
+
523
+ def _filter_environment_variables(
524
+ self, env: dict[str, str], filtered_vars: list[str]
525
+ ) -> dict[str, str]:
526
+ sanitized_env = {}
527
+
528
+ for key, value in env.items():
529
+ if self._is_dangerous_environment_key(key, value, filtered_vars):
530
+ continue
531
+
532
+ if self._is_environment_value_too_long(key, value, filtered_vars):
533
+ continue
534
+
535
+ if self._has_environment_injection(key, value, filtered_vars):
536
+ continue
537
+
538
+ sanitized_env[key] = value
539
+
540
+ return sanitized_env
541
+
542
+ def _is_dangerous_environment_key(
543
+ self, key: str, value: str, filtered_vars: list[str]
544
+ ) -> bool:
545
+ if key in self.dangerous_env_vars:
546
+ filtered_vars.append(key)
547
+ self.security_logger.log_environment_variable_filtered(
548
+ variable_name=key,
549
+ reason="dangerous environment variable",
550
+ value_preview=value[:50] if value else "",
551
+ )
552
+ return True
553
+ return False
554
+
555
+ def _is_environment_value_too_long(
556
+ self, key: str, value: str, filtered_vars: list[str]
557
+ ) -> bool:
558
+ if len(value) > self.config.max_env_var_length:
559
+ filtered_vars.append(key)
560
+ self.security_logger.log_environment_variable_filtered(
561
+ variable_name=key,
562
+ reason=f"value too long: {len(value)} > {self.config.max_env_var_length}",
563
+ value_preview=value[:50],
564
+ )
565
+ return True
566
+ return False
567
+
568
+ def _has_environment_injection(
569
+ self, key: str, value: str, filtered_vars: list[str]
570
+ ) -> bool:
571
+ for pattern in self.dangerous_patterns[:3]:
572
+ if re.search(pattern, value):
573
+ filtered_vars.append(key)
574
+ self.security_logger.log_environment_variable_filtered(
575
+ variable_name=key,
576
+ reason=f"dangerous pattern '{pattern}' in value",
577
+ value_preview=value[:50],
578
+ )
579
+ return True
580
+ return False
581
+
582
+ def _add_safe_environment_variables(self, sanitized_env: dict[str, str]) -> None:
583
+ for safe_var in self.safe_env_vars:
584
+ if safe_var not in sanitized_env and safe_var in os.environ:
585
+ sanitized_env[safe_var] = os.environ[safe_var]
586
+
587
+ def _log_environment_sanitization(
588
+ self, original_count: int, sanitized_count: int, filtered_vars: list[str]
589
+ ) -> None:
590
+ if self.config.enable_command_logging:
591
+ self.security_logger.log_subprocess_environment_sanitized(
592
+ original_count=original_count,
593
+ sanitized_count=sanitized_count,
594
+ filtered_vars=filtered_vars,
595
+ )
596
+
597
+ def _validate_timeout(self, timeout: float | None) -> float | None:
598
+ if timeout is None:
599
+ return None
600
+
601
+ if timeout <= 0:
602
+ raise CommandValidationError(f"Timeout must be positive: {timeout}")
603
+
604
+ if timeout > self.config.max_timeout:
605
+ self.security_logger.log_security_event(
606
+ SecurityEventType.INPUT_SIZE_EXCEEDED,
607
+ SecurityEventLevel.MEDIUM,
608
+ f"Timeout too large: {timeout} > {self.config.max_timeout}",
609
+ requested_timeout=timeout,
610
+ max_timeout=self.config.max_timeout,
611
+ )
612
+ raise CommandValidationError(
613
+ f"Timeout too large: {timeout} > {self.config.max_timeout}"
614
+ )
615
+
616
+ return timeout
617
+
618
+
619
+ _global_executor: SecureSubprocessExecutor | None = None
620
+
621
+
622
+ def get_secure_executor(
623
+ config: SubprocessSecurityConfig | None = None,
624
+ ) -> SecureSubprocessExecutor:
625
+ global _global_executor
626
+ if _global_executor is None:
627
+ _global_executor = SecureSubprocessExecutor(config)
628
+ return _global_executor
629
+
630
+
631
+ def execute_secure_subprocess(
632
+ command: list[str],
633
+ **kwargs: t.Any,
634
+ ) -> subprocess.CompletedProcess[str]:
635
+ return get_secure_executor().execute_secure(command, **kwargs)