patchi 0.7.5__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 (496) hide show
  1. patchi/__init__.py +1 -0
  2. patchi/__main__.py +13 -0
  3. patchi/cli/__init__.py +0 -0
  4. patchi/cli/command_families.py +235 -0
  5. patchi/cli/commands/__init__.py +0 -0
  6. patchi/cli/commands/access_cmd.py +65 -0
  7. patchi/cli/commands/agent_stats_cmd.py +273 -0
  8. patchi/cli/commands/agents_cmd.py +187 -0
  9. patchi/cli/commands/ai_cmd.py +308 -0
  10. patchi/cli/commands/assure_cmd.py +538 -0
  11. patchi/cli/commands/audit_cmd.py +286 -0
  12. patchi/cli/commands/auto_cmd.py +110 -0
  13. patchi/cli/commands/blame_cmd.py +53 -0
  14. patchi/cli/commands/chains_cmd.py +327 -0
  15. patchi/cli/commands/charter_cmd.py +173 -0
  16. patchi/cli/commands/chat_cmd.py +597 -0
  17. patchi/cli/commands/check_cmd.py +232 -0
  18. patchi/cli/commands/cleanup_cmd.py +328 -0
  19. patchi/cli/commands/cockpit_cmd.py +208 -0
  20. patchi/cli/commands/commands_cmd.py +160 -0
  21. patchi/cli/commands/cross_repo_cmd.py +113 -0
  22. patchi/cli/commands/deps_cmd.py +110 -0
  23. patchi/cli/commands/dev_check_cmd.py +342 -0
  24. patchi/cli/commands/dev_cmd.py +445 -0
  25. patchi/cli/commands/doctor_cmd.py +457 -0
  26. patchi/cli/commands/findings_cmd.py +187 -0
  27. patchi/cli/commands/fix_cmd.py +648 -0
  28. patchi/cli/commands/fix_review_cmd.py +273 -0
  29. patchi/cli/commands/heatmap_cmd.py +62 -0
  30. patchi/cli/commands/help_cmd.py +159 -0
  31. patchi/cli/commands/hosted_cmd.py +980 -0
  32. patchi/cli/commands/init.py +456 -0
  33. patchi/cli/commands/key_cmd.py +571 -0
  34. patchi/cli/commands/learn_cmd.py +140 -0
  35. patchi/cli/commands/link_cmd.py +104 -0
  36. patchi/cli/commands/log_cmd.py +57 -0
  37. patchi/cli/commands/memory_cmd.py +215 -0
  38. patchi/cli/commands/model_cmd.py +207 -0
  39. patchi/cli/commands/notify_cmd.py +213 -0
  40. patchi/cli/commands/notify_prompts.py +29 -0
  41. patchi/cli/commands/patch_cmd.py +231 -0
  42. patchi/cli/commands/plan_cmd.py +128 -0
  43. patchi/cli/commands/plugins_cmd.py +307 -0
  44. patchi/cli/commands/queue_cmd.py +152 -0
  45. patchi/cli/commands/quick_cmd.py +159 -0
  46. patchi/cli/commands/ready_cmd.py +292 -0
  47. patchi/cli/commands/report_cmd.py +416 -0
  48. patchi/cli/commands/restrict_cmd.py +127 -0
  49. patchi/cli/commands/review_cmd.py +225 -0
  50. patchi/cli/commands/rules_cmd.py +233 -0
  51. patchi/cli/commands/scan_cmd.py +1908 -0
  52. patchi/cli/commands/settings_cmd.py +45 -0
  53. patchi/cli/commands/status_cmd.py +679 -0
  54. patchi/cli/commands/test_cmd.py +1052 -0
  55. patchi/cli/commands/trend_cmd.py +296 -0
  56. patchi/cli/commands/undo_cmd.py +235 -0
  57. patchi/cli/commands/update_cmd.py +345 -0
  58. patchi/cli/commands/verify_cmd.py +56 -0
  59. patchi/cli/commands/vr_cmd.py +440 -0
  60. patchi/cli/commands/watch_cmd.py +304 -0
  61. patchi/cli/commands/web_cmd.py +114 -0
  62. patchi/cli/console.py +101 -0
  63. patchi/cli/display/__init__.py +1 -0
  64. patchi/cli/display/live_audit.py +269 -0
  65. patchi/cli/display/live_progress.py +169 -0
  66. patchi/cli/framework.py +241 -0
  67. patchi/cli/logo.py +128 -0
  68. patchi/cli/main.py +222 -0
  69. patchi/cli/registry.py +1171 -0
  70. patchi/cli/style.py +68 -0
  71. patchi/cli/themes.py +187 -0
  72. patchi/cli/ux.py +444 -0
  73. patchi/core/__init__.py +0 -0
  74. patchi/core/agents/__init__.py +0 -0
  75. patchi/core/agents/api_fuzzer_agent.py +141 -0
  76. patchi/core/agents/attack_agent.py +242 -0
  77. patchi/core/agents/base.py +1012 -0
  78. patchi/core/agents/bug_predictor_agent.py +58 -0
  79. patchi/core/agents/build_tool_validator.py +150 -0
  80. patchi/core/agents/cache.py +192 -0
  81. patchi/core/agents/chaos_agent.py +133 -0
  82. patchi/core/agents/cicd_generator.py +240 -0
  83. patchi/core/agents/comment_scanner.py +280 -0
  84. patchi/core/agents/console_logging_agent.py +156 -0
  85. patchi/core/agents/coordinator.py +858 -0
  86. patchi/core/agents/core_scanner.py +269 -0
  87. patchi/core/agents/coverage_prioritizer.py +181 -0
  88. patchi/core/agents/dead_code_hygiene.py +296 -0
  89. patchi/core/agents/dead_code_scanner.py +692 -0
  90. patchi/core/agents/dependency_scanner.py +725 -0
  91. patchi/core/agents/doc_claim_agent.py +573 -0
  92. patchi/core/agents/duplicate_scanner.py +568 -0
  93. patchi/core/agents/env_scanner.py +410 -0
  94. patchi/core/agents/feature_flag_agent.py +89 -0
  95. patchi/core/agents/frontend_framework_agent.py +133 -0
  96. patchi/core/agents/goroutine_agent.py +82 -0
  97. patchi/core/agents/governor.py +1972 -0
  98. patchi/core/agents/i18n_agent.py +95 -0
  99. patchi/core/agents/license_compliance.py +247 -0
  100. patchi/core/agents/linking_agent.py +251 -0
  101. patchi/core/agents/memory_profiler_agent.py +102 -0
  102. patchi/core/agents/modernization_agent.py +84 -0
  103. patchi/core/agents/mutation_agent.py +174 -0
  104. patchi/core/agents/nl_query_agent.py +74 -0
  105. patchi/core/agents/promise_rejection_agent.py +100 -0
  106. patchi/core/agents/refactoring_agent.py +286 -0
  107. patchi/core/agents/resource_leak_agent.py +91 -0
  108. patchi/core/agents/route_graph_scanner.py +951 -0
  109. patchi/core/agents/run_agent.py +106 -0
  110. patchi/core/agents/rust_unwrap_agent.py +100 -0
  111. patchi/core/agents/sbom_generator.py +284 -0
  112. patchi/core/agents/scanners.py +67 -0
  113. patchi/core/agents/side/__init__.py +1 -0
  114. patchi/core/agents/side/build_agent.py +65 -0
  115. patchi/core/agents/side/format_agent.py +50 -0
  116. patchi/core/agents/side/install_agent.py +63 -0
  117. patchi/core/agents/side_file_scanner.py +549 -0
  118. patchi/core/agents/snapshot_drift_detector.py +146 -0
  119. patchi/core/agents/spa_route_inventory.py +203 -0
  120. patchi/core/agents/test_scanner.py +442 -0
  121. patchi/core/agents/tool_health.py +109 -0
  122. patchi/core/agents/tool_runner.py +162 -0
  123. patchi/core/agents/type_scanner.py +168 -0
  124. patchi/core/agents/ui_scanner.py +558 -0
  125. patchi/core/ai/__init__.py +20 -0
  126. patchi/core/ai/agent_audit.py +153 -0
  127. patchi/core/ai/agent_profiler.py +277 -0
  128. patchi/core/ai/client.py +499 -0
  129. patchi/core/ai/cost_tracker.py +107 -0
  130. patchi/core/ai/model_router.py +318 -0
  131. patchi/core/ai/orchestrator.py +625 -0
  132. patchi/core/ai/prompts.py +681 -0
  133. patchi/core/ai/smart.py +302 -0
  134. patchi/core/ai/tool_executor.py +24 -0
  135. patchi/core/ai/tools/__init__.py +10 -0
  136. patchi/core/ai/tools/executor.py +621 -0
  137. patchi/core/ai/tools/realize.py +1160 -0
  138. patchi/core/ai/tools/registry.py +1577 -0
  139. patchi/core/assurance/__init__.py +33 -0
  140. patchi/core/assurance/builder.py +211 -0
  141. patchi/core/assurance/graph.py +342 -0
  142. patchi/core/assurance/invariants.py +168 -0
  143. patchi/core/atomic.py +61 -0
  144. patchi/core/attackers/__init__.py +17 -0
  145. patchi/core/attackers/api_abuse.py +89 -0
  146. patchi/core/attackers/auth_bypass.py +73 -0
  147. patchi/core/attackers/base.py +91 -0
  148. patchi/core/attackers/business_logic.py +88 -0
  149. patchi/core/attackers/planner.py +108 -0
  150. patchi/core/attackers/priv_escalation.py +88 -0
  151. patchi/core/attackers/recon.py +87 -0
  152. patchi/core/brain/__init__.py +0 -0
  153. patchi/core/brain/ast_utils/__init__.py +81 -0
  154. patchi/core/brain/ast_utils/assignments.py +219 -0
  155. patchi/core/brain/ast_utils/calls.py +139 -0
  156. patchi/core/brain/ast_utils/config.py +132 -0
  157. patchi/core/brain/ast_utils/control_flow.py +74 -0
  158. patchi/core/brain/ast_utils/dead_symbols.py +205 -0
  159. patchi/core/brain/ast_utils/decorators.py +108 -0
  160. patchi/core/brain/ast_utils/helpers.py +144 -0
  161. patchi/core/brain/ast_utils/imports.py +169 -0
  162. patchi/core/brain/ast_utils/scan.py +143 -0
  163. patchi/core/brain/ast_utils/taint.py +310 -0
  164. patchi/core/brain/audit.py +379 -0
  165. patchi/core/brain/baseline.py +195 -0
  166. patchi/core/brain/blast_radius.py +312 -0
  167. patchi/core/brain/body_tags.py +144 -0
  168. patchi/core/brain/brain.py +1161 -0
  169. patchi/core/brain/brain_context.py +393 -0
  170. patchi/core/brain/brain_watcher.py +340 -0
  171. patchi/core/brain/charter.py +473 -0
  172. patchi/core/brain/classifier.py +214 -0
  173. patchi/core/brain/cockpit.py +350 -0
  174. patchi/core/brain/code_query.py +710 -0
  175. patchi/core/brain/contract.py +889 -0
  176. patchi/core/brain/contract_diff.py +155 -0
  177. patchi/core/brain/council.py +800 -0
  178. patchi/core/brain/cross_repo.py +104 -0
  179. patchi/core/brain/dead_code_tools.py +157 -0
  180. patchi/core/brain/doc_validator.py +197 -0
  181. patchi/core/brain/domain_activator.py +2183 -0
  182. patchi/core/brain/enriched_context.py +220 -0
  183. patchi/core/brain/file_corpus.py +220 -0
  184. patchi/core/brain/framework.py +953 -0
  185. patchi/core/brain/freshness.py +273 -0
  186. patchi/core/brain/git_aware.py +206 -0
  187. patchi/core/brain/heatmap.py +104 -0
  188. patchi/core/brain/ignore_parser.py +153 -0
  189. patchi/core/brain/import_graph.py +486 -0
  190. patchi/core/brain/languages.py +397 -0
  191. patchi/core/brain/layered_brain.py +419 -0
  192. patchi/core/brain/learning.py +229 -0
  193. patchi/core/brain/orphaned_endpoints.py +313 -0
  194. patchi/core/brain/personas/__init__.py +648 -0
  195. patchi/core/brain/personas/bad_user.py +165 -0
  196. patchi/core/brain/personas/base.py +349 -0
  197. patchi/core/brain/proactive.py +807 -0
  198. patchi/core/brain/project_context.py +121 -0
  199. patchi/core/brain/project_reader.py +521 -0
  200. patchi/core/brain/reasoning.py +269 -0
  201. patchi/core/brain/route_detector/__init__.py +15 -0
  202. patchi/core/brain/route_detector/base.py +31 -0
  203. patchi/core/brain/route_detector/csharp.py +209 -0
  204. patchi/core/brain/route_detector/file_based.py +81 -0
  205. patchi/core/brain/route_detector/go.py +152 -0
  206. patchi/core/brain/route_detector/java.py +195 -0
  207. patchi/core/brain/route_detector/javascript.py +197 -0
  208. patchi/core/brain/route_detector/php.py +158 -0
  209. patchi/core/brain/route_detector/python.py +193 -0
  210. patchi/core/brain/route_detector/registry.py +25 -0
  211. patchi/core/brain/route_detector/ruby.py +134 -0
  212. patchi/core/brain/route_detector/rust.py +216 -0
  213. patchi/core/brain/route_detector/swift.py +118 -0
  214. patchi/core/brain/route_mapper.py +265 -0
  215. patchi/core/brain/scanner.py +2136 -0
  216. patchi/core/brain/secrets.py +280 -0
  217. patchi/core/brain/symbol_graph.py +1955 -0
  218. patchi/core/brain/team.py +28 -0
  219. patchi/core/brain/trace_log.py +132 -0
  220. patchi/core/brain/type_checker/__init__.py +65 -0
  221. patchi/core/brain/type_checker/base.py +60 -0
  222. patchi/core/brain/type_checker/csharp.py +59 -0
  223. patchi/core/brain/type_checker/dart.py +59 -0
  224. patchi/core/brain/type_checker/go.py +81 -0
  225. patchi/core/brain/type_checker/java.py +75 -0
  226. patchi/core/brain/type_checker/kotlin.py +75 -0
  227. patchi/core/brain/type_checker/php.py +88 -0
  228. patchi/core/brain/type_checker/python.py +87 -0
  229. patchi/core/brain/type_checker/rust.py +81 -0
  230. patchi/core/brain/type_checker/swift.py +59 -0
  231. patchi/core/brain/type_checker/typescript.py +298 -0
  232. patchi/core/brain/understander.py +130 -0
  233. patchi/core/brain/verify.py +379 -0
  234. patchi/core/campaigns/__init__.py +14 -0
  235. patchi/core/campaigns/base.py +62 -0
  236. patchi/core/campaigns/data_flow_abuse.py +153 -0
  237. patchi/core/campaigns/orchestrator.py +140 -0
  238. patchi/core/campaigns/state_transitions.py +178 -0
  239. patchi/core/ci_bundle.py +104 -0
  240. patchi/core/config.py +311 -0
  241. patchi/core/constants.py +245 -0
  242. patchi/core/debug/__init__.py +25 -0
  243. patchi/core/debug/adapters/__init__.py +11 -0
  244. patchi/core/debug/adapters/codelldb.py +379 -0
  245. patchi/core/debug/adapters/node.py +222 -0
  246. patchi/core/debug/adapters/powershell.py +210 -0
  247. patchi/core/debug/adapters/python.py +244 -0
  248. patchi/core/debug/capture.py +210 -0
  249. patchi/core/debug/dap_client.py +268 -0
  250. patchi/core/detector/__init__.py +0 -0
  251. patchi/core/detector/audit.py +194 -0
  252. patchi/core/detector/bus.py +226 -0
  253. patchi/core/detector/dispatcher.py +275 -0
  254. patchi/core/detector/event.py +254 -0
  255. patchi/core/detector/sigma_engine.py +378 -0
  256. patchi/core/detector/triage.py +289 -0
  257. patchi/core/export/sarif.py +113 -0
  258. patchi/core/fix/__init__.py +17 -0
  259. patchi/core/fix/applier.py +574 -0
  260. patchi/core/fix/base.py +510 -0
  261. patchi/core/fix/code_fixer.py +169 -0
  262. patchi/core/fix/dead_code_remover.py +131 -0
  263. patchi/core/fix/fix_agents.py +775 -0
  264. patchi/core/fix/patch.py +444 -0
  265. patchi/core/fix/risk_gate.py +383 -0
  266. patchi/core/fix/security_fixer.py +93 -0
  267. patchi/core/fix/verify_loop.py +204 -0
  268. patchi/core/fuzz/__init__.py +8 -0
  269. patchi/core/fuzz/corpus.py +137 -0
  270. patchi/core/fuzz/input_fuzzer.py +256 -0
  271. patchi/core/fuzz/sequence_fuzzer.py +162 -0
  272. patchi/core/fuzz/state_fuzzer.py +236 -0
  273. patchi/core/health.py +407 -0
  274. patchi/core/hosted/__init__.py +1 -0
  275. patchi/core/hosted/anomaly.py +298 -0
  276. patchi/core/hosted/audit_log.py +102 -0
  277. patchi/core/hosted/compliance_report.py +148 -0
  278. patchi/core/hosted/ip_reputation.py +169 -0
  279. patchi/core/hosted/log_parsers.py +251 -0
  280. patchi/core/hosted/tokens.py +127 -0
  281. patchi/core/hosted/watchlist.py +164 -0
  282. patchi/core/hosted/webhooks.py +156 -0
  283. patchi/core/memory.py +437 -0
  284. patchi/core/notifications/__init__.py +18 -0
  285. patchi/core/notifications/channels.py +192 -0
  286. patchi/core/notifications/digest.py +141 -0
  287. patchi/core/notifications/escalation.py +158 -0
  288. patchi/core/notifications/notifier.py +182 -0
  289. patchi/core/notifications/quiet_hours.py +69 -0
  290. patchi/core/plugins/__init__.py +48 -0
  291. patchi/core/plugins/analyzer.py +326 -0
  292. patchi/core/plugins/analyzers/__init__.py +6 -0
  293. patchi/core/plugins/analyzers/dead_code_analyzer.py +120 -0
  294. patchi/core/plugins/analyzers/secret_scanner.py +158 -0
  295. patchi/core/plugins/analyzers/todo_scanner.py +115 -0
  296. patchi/core/plugins/registry.py +335 -0
  297. patchi/core/queue.py +375 -0
  298. patchi/core/reliability/__init__.py +13 -0
  299. patchi/core/reliability/chaos.py +163 -0
  300. patchi/core/reliability/fault_injector.py +172 -0
  301. patchi/core/reliability/idempotency.py +128 -0
  302. patchi/core/reliability/recovery.py +147 -0
  303. patchi/core/runtime/__init__.py +5 -0
  304. patchi/core/runtime/tracer.py +230 -0
  305. patchi/core/scan_bus.py +156 -0
  306. patchi/core/security/__init__.py +1 -0
  307. patchi/core/security/ai_validator.py +462 -0
  308. patchi/core/security/app_mapper.py +216 -0
  309. patchi/core/security/app_profile.py +464 -0
  310. patchi/core/security/attack_feedback.py +141 -0
  311. patchi/core/security/attack_scenarios/__init__.py +1 -0
  312. patchi/core/security/attack_tree.py +176 -0
  313. patchi/core/security/auth_audit_agent.py +344 -0
  314. patchi/core/security/authz_agent.py +602 -0
  315. patchi/core/security/auto_fix_proactive.py +620 -0
  316. patchi/core/security/auto_fixer.py +660 -0
  317. patchi/core/security/auto_ticket.py +53 -0
  318. patchi/core/security/bandit_agent.py +152 -0
  319. patchi/core/security/blast_radius.py +226 -0
  320. patchi/core/security/browser_tester.py +333 -0
  321. patchi/core/security/business_logic_agent.py +263 -0
  322. patchi/core/security/catch_block_auditor.py +395 -0
  323. patchi/core/security/cdn_cache_agent.py +357 -0
  324. patchi/core/security/chain_analyzer.py +321 -0
  325. patchi/core/security/chain_to_assurance.py +168 -0
  326. patchi/core/security/charter.py +510 -0
  327. patchi/core/security/cloud_waf_detector.py +295 -0
  328. patchi/core/security/codeql_agent.py +233 -0
  329. patchi/core/security/compliance_agent.py +348 -0
  330. patchi/core/security/confidence_gate.py +423 -0
  331. patchi/core/security/container_scanner.py +282 -0
  332. patchi/core/security/crypto_agent.py +378 -0
  333. patchi/core/security/cve_monitor.py +262 -0
  334. patchi/core/security/dast_agent.py +865 -0
  335. patchi/core/security/defectdojo.py +238 -0
  336. patchi/core/security/defenders/__init__.py +75 -0
  337. patchi/core/security/defenders/auth_middleware.py +115 -0
  338. patchi/core/security/defenders/base.py +97 -0
  339. patchi/core/security/defenders/block_ip.py +71 -0
  340. patchi/core/security/defenders/block_ws_origin.py +36 -0
  341. patchi/core/security/defenders/crypto_fix.py +118 -0
  342. patchi/core/security/defenders/enforce_rate_limit.py +108 -0
  343. patchi/core/security/defenders/fix_code.py +93 -0
  344. patchi/core/security/defenders/invalidate_session.py +40 -0
  345. patchi/core/security/defenders/patch_config.py +70 -0
  346. patchi/core/security/defenders/rotate_secret.py +54 -0
  347. patchi/core/security/defenders/suspend_account.py +41 -0
  348. patchi/core/security/defenders/update_dependency.py +160 -0
  349. patchi/core/security/defense_layer.py +217 -0
  350. patchi/core/security/dependency_vulnerability_agent.py +616 -0
  351. patchi/core/security/detection_pipeline.py +289 -0
  352. patchi/core/security/dns_security_agent.py +497 -0
  353. patchi/core/security/domain_activator_v2.py +913 -0
  354. patchi/core/security/domain_loader.py +1004 -0
  355. patchi/core/security/domains/__init__.py +0 -0
  356. patchi/core/security/email_authentication_agent.py +417 -0
  357. patchi/core/security/env_var_validator.py +304 -0
  358. patchi/core/security/evidence.py +100 -0
  359. patchi/core/security/falco_runtime_agent.py +387 -0
  360. patchi/core/security/fix-playbooks/__init__.py +0 -0
  361. patchi/core/security/gated_finding.py +85 -0
  362. patchi/core/security/git_diff_activator.py +367 -0
  363. patchi/core/security/governance.py +208 -0
  364. patchi/core/security/gradual_gate.py +34 -0
  365. patchi/core/security/history.py +315 -0
  366. patchi/core/security/iac_scanner.py +250 -0
  367. patchi/core/security/ignore_expiry.py +111 -0
  368. patchi/core/security/ignore_learner.py +416 -0
  369. patchi/core/security/injection_agent.py +319 -0
  370. patchi/core/security/insecure_randomness_agent.py +288 -0
  371. patchi/core/security/intent_analyzer.py +326 -0
  372. patchi/core/security/jwt_agent.py +453 -0
  373. patchi/core/security/kubernetes_agent.py +638 -0
  374. patchi/core/security/layer2_orchestrator.py +341 -0
  375. patchi/core/security/llm_security_agent.py +252 -0
  376. patchi/core/security/misconfig_agent.py +600 -0
  377. patchi/core/security/mobile_security_agent.py +474 -0
  378. patchi/core/security/network_agent.py +376 -0
  379. patchi/core/security/noise_filter.py +274 -0
  380. patchi/core/security/orchestrator.py +313 -0
  381. patchi/core/security/pattern_context.py +125 -0
  382. patchi/core/security/pattern_loader.py +101 -0
  383. patchi/core/security/pentest/__init__.py +4 -0
  384. patchi/core/security/pentest/base.py +58 -0
  385. patchi/core/security/pentest/dalfox_adapter.py +40 -0
  386. patchi/core/security/pentest/ffuf_adapter.py +48 -0
  387. patchi/core/security/pentest/nuclei_adapter.py +50 -0
  388. patchi/core/security/pentest/registry.py +80 -0
  389. patchi/core/security/pentest/shannon_adapter.py +107 -0
  390. patchi/core/security/pentest/sqlmap_adapter.py +56 -0
  391. patchi/core/security/pentest/zap_adapter.py +52 -0
  392. patchi/core/security/plan_auditor.py +276 -0
  393. patchi/core/security/policy_engine.py +272 -0
  394. patchi/core/security/prechecks.py +172 -0
  395. patchi/core/security/precommit_hook.py +247 -0
  396. patchi/core/security/privacy_agent.py +361 -0
  397. patchi/core/security/push_notification_agent.py +417 -0
  398. patchi/core/security/pysa_agent.py +143 -0
  399. patchi/core/security/reasoning.py +442 -0
  400. patchi/core/security/red_team_agent.py +184 -0
  401. patchi/core/security/red_team_engine.py +1142 -0
  402. patchi/core/security/remediation.py +360 -0
  403. patchi/core/security/request_interceptor.py +294 -0
  404. patchi/core/security/runtime_validator.py +245 -0
  405. patchi/core/security/saml_sso_agent.py +401 -0
  406. patchi/core/security/sast_agent.py +213 -0
  407. patchi/core/security/sast_gate.py +95 -0
  408. patchi/core/security/scheduler.py +287 -0
  409. patchi/core/security/secrets_runtime_agent.py +476 -0
  410. patchi/core/security/security_agents.py +175 -0
  411. patchi/core/security/security_config.py +551 -0
  412. patchi/core/security/security_probe.py +762 -0
  413. patchi/core/security/security_taint.py +1005 -0
  414. patchi/core/security/sensitive_data_agent.py +271 -0
  415. patchi/core/security/service_mesh_agent.py +412 -0
  416. patchi/core/security/session_management_agent.py +245 -0
  417. patchi/core/security/ssrf_agent.py +422 -0
  418. patchi/core/security/supply_chain.py +449 -0
  419. patchi/core/security/threat_model_generator.py +378 -0
  420. patchi/core/security/threat_model_updater.py +196 -0
  421. patchi/core/security/tool_adapters.py +135 -0
  422. patchi/core/security/tool_verify.py +173 -0
  423. patchi/core/security/websocket_security_agent.py +188 -0
  424. patchi/core/snapshot.py +242 -0
  425. patchi/core/tenant.py +386 -0
  426. patchi/core/testing/__init__.py +17 -0
  427. patchi/core/testing/_browser.py +147 -0
  428. patchi/core/testing/ads_agent.py +141 -0
  429. patchi/core/testing/api_contract_agent.py +450 -0
  430. patchi/core/testing/app_discovery_agent.py +641 -0
  431. patchi/core/testing/app_launcher.py +184 -0
  432. patchi/core/testing/browser_test_agent.py +516 -0
  433. patchi/core/testing/e2e_flow_agent.py +354 -0
  434. patchi/core/testing/flake_detector_agent.py +292 -0
  435. patchi/core/testing/gate.py +31 -0
  436. patchi/core/testing/live_test_runner.py +304 -0
  437. patchi/core/testing/live_v2/__init__.py +1 -0
  438. patchi/core/testing/live_v2/browser_pool.py +349 -0
  439. patchi/core/testing/live_v2/browser_test_runner.py +313 -0
  440. patchi/core/testing/live_v2/runner.py +626 -0
  441. patchi/core/testing/live_v2/screenshot_manager.py +474 -0
  442. patchi/core/testing/live_v2/stress_orchestrator.py +523 -0
  443. patchi/core/testing/live_v2/video_recorder.py +303 -0
  444. patchi/core/testing/regression_agent.py +302 -0
  445. patchi/core/testing/security_test_agent.py +253 -0
  446. patchi/core/testing/stress_test_agent.py +203 -0
  447. patchi/core/testing/test_agents.py +50 -0
  448. patchi/core/testing/test_config.py +194 -0
  449. patchi/core/testing/ui_accessibility_agent.py +324 -0
  450. patchi/core/testing/ui_button_agent.py +294 -0
  451. patchi/core/testing/ui_layout_agent.py +330 -0
  452. patchi/core/testing/unit_test_agent.py +1149 -0
  453. patchi/core/testing/visual_regression_agent.py +260 -0
  454. patchi/web/__init__.py +1 -0
  455. patchi/web/api/__init__.py +1 -0
  456. patchi/web/api/brain_map.py +114 -0
  457. patchi/web/api/charts.py +47 -0
  458. patchi/web/api/chat.py +224 -0
  459. patchi/web/api/cicd.py +576 -0
  460. patchi/web/api/dast_evidence.py +49 -0
  461. patchi/web/api/dev_check.py +320 -0
  462. patchi/web/api/fix.py +134 -0
  463. patchi/web/api/guard.py +65 -0
  464. patchi/web/api/hosted.py +128 -0
  465. patchi/web/api/hosted_v2.py +234 -0
  466. patchi/web/api/live_testing.py +699 -0
  467. patchi/web/api/scan.py +582 -0
  468. patchi/web/api/smart.py +111 -0
  469. patchi/web/api/tenant.py +117 -0
  470. patchi/web/api/tools.py +85 -0
  471. patchi/web/api_legacy.py +2401 -0
  472. patchi/web/app.py +310 -0
  473. patchi/web/events.py +483 -0
  474. patchi/web/routes/__init__.py +1 -0
  475. patchi/web/routes/assurance.py +541 -0
  476. patchi/web/routes/brain.py +36 -0
  477. patchi/web/routes/charter.py +227 -0
  478. patchi/web/routes/chat.py +23 -0
  479. patchi/web/routes/dashboard.py +558 -0
  480. patchi/web/routes/dashboard_v2.py +402 -0
  481. patchi/web/routes/findings.py +330 -0
  482. patchi/web/routes/guard.py +35 -0
  483. patchi/web/routes/history.py +126 -0
  484. patchi/web/routes/landing.py +52 -0
  485. patchi/web/routes/live_testing.py +24 -0
  486. patchi/web/routes/review.py +57 -0
  487. patchi/web/routes/self_improvement.py +110 -0
  488. patchi/web/routes/settings.py +49 -0
  489. patchi/web/routes/tokens.py +29 -0
  490. patchi/web/spawn.py +139 -0
  491. patchi/web/ws.py +141 -0
  492. patchi-0.7.5.dist-info/METADATA +315 -0
  493. patchi-0.7.5.dist-info/RECORD +496 -0
  494. patchi-0.7.5.dist-info/WHEEL +5 -0
  495. patchi-0.7.5.dist-info/licenses/LICENSE +60 -0
  496. patchi-0.7.5.dist-info/top_level.txt +1 -0
patchi/core/queue.py ADDED
@@ -0,0 +1,375 @@
1
+ """
2
+ Queue system for Patchi.
3
+
4
+ All tasks go through the queue. Nothing runs immediately.
5
+ Queue state persists to .patchi/queue.json between CLI invocations.
6
+
7
+ Three modes:
8
+ single — one task at a time, safest for low-end hardware
9
+ multi — parallel up to hardware-detected limit
10
+ off — no queue, tasks run immediately (power user mode)
11
+
12
+ The queue stores task definitions as JSON records.
13
+ Actual task execution is wired up in Agent System.
14
+ The queue here is the data layer + control layer — fully functional.
15
+ """
16
+
17
+ import json
18
+ import platform
19
+ import uuid
20
+ from contextlib import contextmanager
21
+ from datetime import UTC, datetime
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ from patchi.core.config import require_project_root
26
+ from patchi.core.constants import (
27
+ QUEUE_FILE,
28
+ QUEUE_MAX_DEPTH,
29
+ QUEUE_WARN_DEPTH,
30
+ QueueItemState,
31
+ )
32
+
33
+ # ── Cross-platform file lock ──────────────────────────────────────────────────
34
+
35
+
36
+ @contextmanager
37
+ def _file_lock(path: Path):
38
+ """Simple cross-platform file lock using lock file + atomic create."""
39
+ lock_path = path.with_suffix(".lock")
40
+ lock_path.parent.mkdir(parents=True, exist_ok=True)
41
+ # Ensure lock file exists before opening
42
+ lock_path.touch(exist_ok=True)
43
+
44
+ if platform.system() == "Windows":
45
+ # Windows: use msvcrt for file locking
46
+ import msvcrt
47
+
48
+ f = open(lock_path, "r+")
49
+ try:
50
+ msvcrt.locking(f.fileno(), msvcrt.LK_LOCK, 1)
51
+ yield
52
+ finally:
53
+ try:
54
+ msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, 1)
55
+ except OSError:
56
+ pass
57
+ f.close()
58
+ else:
59
+ # Unix: use fcntl
60
+ import fcntl
61
+
62
+ f = open(lock_path, "r+")
63
+ try:
64
+ fcntl.flock(f.fileno(), fcntl.LOCK_EX)
65
+ yield
66
+ finally:
67
+ fcntl.flock(f.fileno(), fcntl.LOCK_UN)
68
+ f.close()
69
+
70
+
71
+ # ── Data model ─────────────────────────────────────────────────────────────────
72
+ #
73
+ # Queue file structure:
74
+ # {
75
+ # "paused": false,
76
+ # "items": [
77
+ # {
78
+ # "id": "a1b2c3",
79
+ # "type": "scan | fix | test | security | ...",
80
+ # "target": "full | src/auth/ | ...",
81
+ # "state": "waiting | active | done | failed | skipped",
82
+ # "created": "2025-01-01T00:00:00+00:00",
83
+ # "started": null,
84
+ # "completed": null,
85
+ # "result": null,
86
+ # "error": null,
87
+ # "agent": null,
88
+ # "priority": 0
89
+ # }
90
+ # ]
91
+ # }
92
+
93
+
94
+ def _queue_path(root: Path) -> Path:
95
+ return root / QUEUE_FILE
96
+
97
+
98
+ def _root(r: Path | None) -> Path:
99
+ return r or require_project_root()
100
+
101
+
102
+ # ── File I/O ───────────────────────────────────────────────────────────────────
103
+
104
+
105
+ def _read(root: Path) -> dict:
106
+ path = _queue_path(root)
107
+ if not path.exists():
108
+ return {"paused": False, "items": []}
109
+ with path.open("r", encoding="utf-8") as f:
110
+ return json.load(f)
111
+
112
+
113
+ def _write(root: Path, data: dict) -> None:
114
+ path = _queue_path(root)
115
+ path.parent.mkdir(parents=True, exist_ok=True)
116
+ tmp = path.with_suffix(".tmp")
117
+ with tmp.open("w", encoding="utf-8") as f:
118
+ json.dump(data, f, indent=2)
119
+ tmp.replace(path)
120
+
121
+
122
+ # ── Queue CRUD ─────────────────────────────────────────────────────────────────
123
+
124
+
125
+ def enqueue(
126
+ task_type: str,
127
+ target: str = "full",
128
+ agent: str | None = None,
129
+ priority: int = 0,
130
+ extra: dict | None = None,
131
+ root: Path | None = None,
132
+ ) -> str:
133
+ """
134
+ Add a task to the queue. Returns the new item's ID.
135
+ Raises QueueFullError if the queue is at max depth.
136
+ Warns (via return metadata) if approaching warn depth.
137
+ """
138
+ r = _root(root)
139
+ queue_path = r / QUEUE_FILE
140
+ with _file_lock(queue_path):
141
+ data = _read(r)
142
+
143
+ waiting = [i for i in data["items"] if i["state"] == QueueItemState.WAITING.value]
144
+ if len(waiting) >= QUEUE_MAX_DEPTH:
145
+ raise QueueFullError(
146
+ f"Queue is full ({QUEUE_MAX_DEPTH} items). "
147
+ "Clear some tasks or increase queue_max_depth in settings."
148
+ )
149
+
150
+ item_id = str(uuid.uuid4())[:8]
151
+ item: dict[str, Any] = {
152
+ "id": item_id,
153
+ "type": task_type,
154
+ "target": target,
155
+ "state": QueueItemState.WAITING.value,
156
+ "created": _now(),
157
+ "started": None,
158
+ "completed": None,
159
+ "result": None,
160
+ "error": None,
161
+ "agent": agent,
162
+ "priority": priority,
163
+ **(extra or {}),
164
+ }
165
+
166
+ data["items"].append(item)
167
+ _write(r, data)
168
+ return item_id
169
+
170
+
171
+ def get_item(item_id: str, root: Path | None = None) -> dict | None:
172
+ data = _read(_root(root))
173
+ for item in data["items"]:
174
+ if item["id"] == item_id:
175
+ return item
176
+ return None
177
+
178
+
179
+ def list_items(
180
+ state: QueueItemState | None = None,
181
+ root: Path | None = None,
182
+ ) -> list[dict]:
183
+ """Return all items, optionally filtered by state."""
184
+ data = _read(_root(root))
185
+ items = data["items"]
186
+ if state is not None:
187
+ items = [i for i in items if i["state"] == state.value]
188
+ return items
189
+
190
+
191
+ def list_all(root: Path | None = None) -> dict:
192
+ """Return full queue data including paused flag and all items."""
193
+ return _read(_root(root))
194
+
195
+
196
+ # ── State transitions ──────────────────────────────────────────────────────────
197
+
198
+
199
+ def mark_active(item_id: str, root: Path | None = None) -> bool:
200
+ return _update_item(
201
+ item_id,
202
+ {
203
+ "state": QueueItemState.ACTIVE.value,
204
+ "started": _now(),
205
+ },
206
+ root,
207
+ )
208
+
209
+
210
+ def mark_done(item_id: str, result: Any = None, root: Path | None = None) -> bool:
211
+ return _update_item(
212
+ item_id,
213
+ {
214
+ "state": QueueItemState.DONE.value,
215
+ "completed": _now(),
216
+ "result": result,
217
+ },
218
+ root,
219
+ )
220
+
221
+
222
+ def mark_failed(item_id: str, error: str, root: Path | None = None) -> bool:
223
+ return _update_item(
224
+ item_id,
225
+ {
226
+ "state": QueueItemState.FAILED.value,
227
+ "completed": _now(),
228
+ "error": error,
229
+ },
230
+ root,
231
+ )
232
+
233
+
234
+ def mark_skipped(item_id: str, root: Path | None = None) -> bool:
235
+ return _update_item(
236
+ item_id,
237
+ {
238
+ "state": QueueItemState.SKIPPED.value,
239
+ "completed": _now(),
240
+ },
241
+ root,
242
+ )
243
+
244
+
245
+ def _update_item(item_id: str, updates: dict, root: Path | None = None) -> bool:
246
+ r = _root(root)
247
+ queue_path = r / QUEUE_FILE
248
+ with _file_lock(queue_path):
249
+ data = _read(r)
250
+ for item in data["items"]:
251
+ if item["id"] == item_id:
252
+ item.update(updates)
253
+ _write(r, data)
254
+ return True
255
+ return False
256
+
257
+
258
+ # ── Control ────────────────────────────────────────────────────────────────────
259
+
260
+
261
+ def pause(root: Path | None = None) -> None:
262
+ r = _root(root)
263
+ queue_path = r / QUEUE_FILE
264
+ with _file_lock(queue_path):
265
+ data = _read(r)
266
+ data["paused"] = True
267
+ _write(r, data)
268
+
269
+
270
+ def resume(root: Path | None = None) -> None:
271
+ r = _root(root)
272
+ queue_path = r / QUEUE_FILE
273
+ with _file_lock(queue_path):
274
+ data = _read(r)
275
+ data["paused"] = False
276
+ _write(r, data)
277
+
278
+
279
+ def is_paused(root: Path | None = None) -> bool:
280
+ return _read(_root(root)).get("paused", False)
281
+
282
+
283
+ def skip_active(root: Path | None = None) -> str | None:
284
+ """Mark the current active item as skipped. Returns its ID or None."""
285
+ r = _root(root)
286
+ queue_path = r / QUEUE_FILE
287
+ with _file_lock(queue_path):
288
+ data = _read(r)
289
+ for item in data["items"]:
290
+ if item["state"] == QueueItemState.ACTIVE.value:
291
+ item["state"] = QueueItemState.SKIPPED.value
292
+ item["completed"] = _now()
293
+ _write(r, data)
294
+ return item["id"]
295
+ return None
296
+
297
+
298
+ def clear(root: Path | None = None) -> int:
299
+ """
300
+ Remove all WAITING items. Active/done/failed are preserved for history.
301
+ Returns the number of items removed.
302
+ """
303
+ r = _root(root)
304
+ data = _read(r)
305
+ before = len([i for i in data["items"] if i["state"] == QueueItemState.WAITING.value])
306
+ data["items"] = [i for i in data["items"] if i["state"] != QueueItemState.WAITING.value]
307
+ _write(r, data)
308
+ return before
309
+
310
+
311
+ def clear_all(root: Path | None = None) -> int:
312
+ """Remove everything including done/failed history. Returns count removed."""
313
+ r = _root(root)
314
+ data = _read(r)
315
+ count = len(data["items"])
316
+ data["items"] = []
317
+ _write(r, data)
318
+ return count
319
+
320
+
321
+ # ── Stats ──────────────────────────────────────────────────────────────────────
322
+
323
+
324
+ def depth(root: Path | None = None) -> int:
325
+ """Number of WAITING items."""
326
+ return len(list_items(QueueItemState.WAITING, root))
327
+
328
+
329
+ def is_near_limit(root: Path | None = None) -> bool:
330
+ return depth(root) >= QUEUE_WARN_DEPTH
331
+
332
+
333
+ def stats(root: Path | None = None) -> dict:
334
+ data = _read(_root(root))
335
+ counts: dict[str, int] = {s.value: 0 for s in QueueItemState}
336
+ for item in data["items"]:
337
+ counts[item["state"]] = counts.get(item["state"], 0) + 1
338
+ return {
339
+ "paused": data.get("paused", False),
340
+ "total": len(data["items"]),
341
+ **counts,
342
+ }
343
+
344
+
345
+ # ── Next item picker ─────────────────────────────────
346
+
347
+
348
+ def next_waiting(root: Path | None = None) -> dict | None:
349
+ """
350
+ Return the next WAITING item (highest priority first, then FIFO).
351
+ Returns None if queue is empty or paused.
352
+ """
353
+ r = _root(root)
354
+ if is_paused(r):
355
+ return None
356
+ waiting = list_items(QueueItemState.WAITING, r)
357
+ if not waiting:
358
+ return None
359
+ # Sort by priority descending, then created ascending
360
+ waiting.sort(key=lambda i: (-i.get("priority", 0), i.get("created", "")))
361
+ return waiting[0]
362
+
363
+
364
+ # ── Error types ────────────────────────────────────────────────────────────────
365
+
366
+
367
+ class QueueFullError(Exception):
368
+ pass
369
+
370
+
371
+ # ── Helpers ────────────────────────────────────────────────────────────────────
372
+
373
+
374
+ def _now() -> str:
375
+ return datetime.now(UTC).isoformat()
@@ -0,0 +1,13 @@
1
+ """Reliability testing module — fault injection, chaos, idempotency, recovery."""
2
+
3
+ from .chaos import ChaosScenario
4
+ from .fault_injector import FaultInjector
5
+ from .idempotency import IdempotencyAnalyzer
6
+ from .recovery import RecoveryAnalyzer
7
+
8
+ __all__ = [
9
+ "FaultInjector",
10
+ "ChaosScenario",
11
+ "IdempotencyAnalyzer",
12
+ "RecoveryAnalyzer",
13
+ ]
@@ -0,0 +1,163 @@
1
+ """ChaosScenario — identify chaos engineering opportunities.
2
+
3
+ Maps data flows and identifies points where failures would cascade,
4
+ suggesting chaos experiments to validate resilience.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+
13
+ from patchi.core.assurance.graph import AssuranceGraph
14
+
15
+ _log = logging.getLogger("patchi.reliability.chaos")
16
+
17
+
18
+ @dataclass
19
+ class ChaosExperiment:
20
+ """A suggested chaos experiment."""
21
+
22
+ name: str
23
+ target: str # file or function
24
+ fault_type: str # "network_partition", "disk_full", "cpu_spike", "memory_pressure"
25
+ description: str
26
+ blast_radius: str # "low", "medium", "high"
27
+ expected_impact: str
28
+ recovery_check: str
29
+
30
+ def to_dict(self) -> dict:
31
+ return {
32
+ "name": self.name,
33
+ "target": self.target,
34
+ "fault_type": self.fault_type,
35
+ "description": self.description,
36
+ "blast_radius": self.blast_radius,
37
+ "expected_impact": self.expected_impact,
38
+ "recovery_check": self.recovery_check,
39
+ }
40
+
41
+
42
+ class ChaosScenario:
43
+ """Analyze the project for chaos engineering opportunities."""
44
+
45
+ def __init__(self, root: Path, graph: AssuranceGraph | None = None):
46
+ self._root = root
47
+ self._graph = graph
48
+ self._flows: list[dict] = []
49
+
50
+ def load_flows(self, flows: list[dict]) -> None:
51
+ """Load data flows from brain data."""
52
+ self._flows = flows
53
+
54
+ def analyze(self, max_experiments: int = 20) -> list[ChaosExperiment]:
55
+ """Generate chaos experiments based on the project's data flows."""
56
+ experiments: list[ChaosExperiment] = []
57
+
58
+ # Analyze from graph if available
59
+ if self._graph:
60
+ experiments.extend(self._graph_based_experiments())
61
+
62
+ # Analyze from data flows
63
+ experiments.extend(self._flow_based_experiments())
64
+
65
+ # General resilience checks
66
+ experiments.extend(self._general_resilience_experiments())
67
+
68
+ return experiments[:max_experiments]
69
+
70
+ def _graph_based_experiments(self) -> list[ChaosExperiment]:
71
+ """Generate experiments from assurance graph data flows."""
72
+ experiments: list[ChaosExperiment] = []
73
+
74
+ # Find critical paths (data flows through auth boundaries)
75
+ for claim in self._graph.claims.values():
76
+ for ev in claim.evidence:
77
+ if not ev.supports:
78
+ continue
79
+ artifact = ev.artifact
80
+ if "sink" in artifact and "source" in artifact:
81
+ source = artifact["source"]
82
+ sink = artifact["sink"]
83
+ experiments.append(
84
+ ChaosExperiment(
85
+ name=f"partition_{source}_to_{sink}",
86
+ target=source,
87
+ fault_type="network_partition",
88
+ description=f"Simulate network partition between {source} and {sink}",
89
+ blast_radius="medium",
90
+ expected_impact=f"Requests from {source} to {sink} should timeout gracefully",
91
+ recovery_check=f"Verify {sink} returns 503 or cached response",
92
+ )
93
+ )
94
+
95
+ return experiments
96
+
97
+ def _flow_based_experiments(self) -> list[ChaosExperiment]:
98
+ """Generate experiments from data flow analysis."""
99
+ experiments: list[ChaosExperiment] = []
100
+
101
+ # Group flows by source
102
+ by_source: dict[str, list[dict]] = {}
103
+ for flow in self._flows:
104
+ source = flow.get("source", "unknown")
105
+ by_source.setdefault(source, []).append(flow)
106
+
107
+ # For each source with multiple outgoing flows, test dependency failure
108
+ for source, flows in by_source.items():
109
+ if len(flows) >= 2:
110
+ experiments.append(
111
+ ChaosExperiment(
112
+ name=f"dependency_failure_{source}",
113
+ target=source,
114
+ fault_type="dependency_failure",
115
+ description=f"Simulate failure of one dependency of {source} (has {len(flows)} outgoing flows)",
116
+ blast_radius="medium",
117
+ expected_impact=f"{source} should degrade gracefully when one dependency fails",
118
+ recovery_check="Verify partial results are returned, not a crash",
119
+ )
120
+ )
121
+
122
+ return experiments
123
+
124
+ def _general_resilience_experiments(self) -> list[ChaosExperiment]:
125
+ """Generate general resilience experiments."""
126
+ return [
127
+ ChaosExperiment(
128
+ name="disk_full_on_scan",
129
+ target=".patchi/",
130
+ fault_type="disk_full",
131
+ description="Simulate disk full during scan result persistence",
132
+ blast_radius="low",
133
+ expected_impact="Scan should complete in memory and warn about persistence failure",
134
+ recovery_check="Verify scan results are still available in memory",
135
+ ),
136
+ ChaosExperiment(
137
+ name="memory_pressure_large_project",
138
+ target="scanner",
139
+ fault_type="memory_pressure",
140
+ description="Test scanner behavior with very large file corpus",
141
+ blast_radius="medium",
142
+ expected_impact="Scanner should process files in batches, not load all into memory",
143
+ recovery_check="Verify scan completes without OOM",
144
+ ),
145
+ ChaosExperiment(
146
+ name="database_locked",
147
+ target="history",
148
+ fault_type="database_lock",
149
+ description="Simulate SQLite database locked by another process",
150
+ blast_radius="low",
151
+ expected_impact="History writes should retry or queue, not crash",
152
+ recovery_check="Verify scan completes and results are eventually persisted",
153
+ ),
154
+ ChaosExperiment(
155
+ name="network_timeout_ai",
156
+ target="llm_security_agent",
157
+ fault_type="network_partition",
158
+ description="Simulate AI provider timeout during LLM security analysis",
159
+ blast_radius="low",
160
+ expected_impact="LLM analysis should timeout gracefully, deterministic agents still work",
161
+ recovery_check="Verify deterministic findings are still produced",
162
+ ),
163
+ ]