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
@@ -0,0 +1,474 @@
1
+ """
2
+ Screenshot Manager — Captures and compares screenshots for visual regression testing.
3
+
4
+ Features:
5
+ - Full page and element screenshots
6
+ - Baseline management
7
+ - Pixel-perfect comparison with configurable thresholds
8
+ - Diff image generation
9
+ - Multi-browser support
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import base64
15
+ import hashlib
16
+ import logging
17
+ from collections.abc import Callable
18
+ from dataclasses import dataclass, field
19
+ from datetime import UTC, datetime
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ _log = logging.getLogger("patchi.testing.screenshot_manager")
24
+
25
+
26
+ @dataclass
27
+ class ScreenshotConfig:
28
+ """Configuration for screenshot capture."""
29
+
30
+ viewport: dict = field(default_factory=lambda: {"width": 1280, "height": 720})
31
+ full_page: bool = True
32
+ wait_for: str | None = None # CSS selector to wait for
33
+ wait_timeout: int = 10000 # ms
34
+ animations: str = "disabled" # "disabled", "allow"
35
+ scale: float = 1.0 # Device scale factor
36
+ mask: list[str] = field(default_factory=list) # CSS selectors to mask
37
+ clip: dict | None = None # {x, y, width, height}
38
+
39
+
40
+ @dataclass
41
+ class ScreenshotResult:
42
+ """Result of a screenshot capture."""
43
+
44
+ url: str
45
+ selector: str | None
46
+ timestamp: str
47
+ image_base64: str
48
+ image_path: str | None
49
+ dimensions: dict # width, height
50
+ file_size: int
51
+ config: ScreenshotConfig
52
+
53
+
54
+ @dataclass
55
+ class VisualDiffResult:
56
+ """Result of visual comparison."""
57
+
58
+ baseline_path: str
59
+ current_path: str
60
+ diff_path: str | None
61
+ passed: bool
62
+ difference_percent: float
63
+ threshold_percent: float
64
+ mismatch_pixels: int
65
+ total_pixels: int
66
+
67
+
68
+ class ScreenshotManager:
69
+ """
70
+ Manages screenshot capture and visual regression testing.
71
+
72
+ Usage:
73
+ manager = ScreenshotManager(baseline_dir="baselines")
74
+
75
+ # Capture screenshot
76
+ result = await manager.capture(page, "https://example.com")
77
+
78
+ # Compare with baseline
79
+ diff = await manager.compare("homepage", result.image_base64)
80
+ """
81
+
82
+ def __init__(
83
+ self,
84
+ baseline_dir: str | Path = ".patchi/visual_baselines",
85
+ threshold: float = 0.1, # 10% difference allowed
86
+ on_progress: Callable[[str], None] | None = None,
87
+ ):
88
+ self.baseline_dir = Path(baseline_dir)
89
+ self.baseline_dir.mkdir(parents=True, exist_ok=True)
90
+ self.threshold = threshold
91
+ self.on_progress = on_progress or (lambda _: None)
92
+ self._pixelmatch = None
93
+
94
+ async def _get_pixelmatch(self):
95
+ """Lazy load pixelmatch for image comparison."""
96
+ if self._pixelmatch is None:
97
+ try:
98
+ import pixelmatch
99
+
100
+ self._pixelmatch = pixelmatch
101
+ except ImportError:
102
+ try:
103
+ from PIL import Image, ImageChops # noqa: F401 — availability check
104
+
105
+ self._pixelmatch = "PIL"
106
+ except ImportError:
107
+ _log.warning("Neither pixelmatch nor PIL available for image comparison")
108
+ return self._pixelmatch
109
+
110
+ async def capture(
111
+ self,
112
+ page: Any,
113
+ url: str,
114
+ name: str = None,
115
+ config: ScreenshotConfig = None,
116
+ selector: str = None,
117
+ ) -> ScreenshotResult:
118
+ """
119
+ Capture a screenshot of a page or element.
120
+
121
+ Args:
122
+ page: Playwright page object
123
+ url: URL to navigate to (or current page if already there)
124
+ name: Optional name for the screenshot
125
+ config: Screenshot configuration
126
+ selector: Optional CSS selector for element screenshot
127
+
128
+ Returns:
129
+ ScreenshotResult with base64 image and metadata
130
+ """
131
+ config = config or ScreenshotConfig()
132
+ name = name or self._generate_name(url, selector)
133
+
134
+ self.on_progress(f"📸 Capturing screenshot: {name}")
135
+
136
+ # Navigate if needed
137
+ if page.url != url:
138
+ await page.goto(url, wait_until="networkidle")
139
+
140
+ # Wait for selector if specified
141
+ if config.wait_for:
142
+ try:
143
+ await page.wait_for_selector(config.wait_for, timeout=config.wait_timeout)
144
+ except Exception:
145
+ _log.warning(f"Wait for selector timeout: {config.wait_for}")
146
+
147
+ # Disable animations if configured
148
+ if config.animations == "disabled":
149
+ await page.add_style_tag(
150
+ content="""
151
+ *, *::before, *::after {
152
+ animation-duration: 0s !important;
153
+ animation-delay: 0s !important;
154
+ transition-duration: 0s !important;
155
+ transition-delay: 0s !important;
156
+ }
157
+ """
158
+ )
159
+
160
+ # Mask elements if specified
161
+ for mask_selector in config.mask:
162
+ await page.add_style_tag(
163
+ content=f"""
164
+ {mask_selector} {{
165
+ visibility: hidden !important;
166
+ }}
167
+ """
168
+ )
169
+
170
+ # Capture screenshot
171
+ screenshot_options = {
172
+ "full_page": config.full_page,
173
+ "scale": "css" if config.scale == 1.0 else "device",
174
+ }
175
+
176
+ if config.clip:
177
+ screenshot_options["clip"] = config.clip
178
+
179
+ if selector:
180
+ element = await page.query_selector(selector)
181
+ if not element:
182
+ raise ValueError(f"Element not found: {selector}")
183
+ screenshot_bytes = await element.screenshot(**screenshot_options)
184
+ else:
185
+ screenshot_bytes = await page.screenshot(**screenshot_options)
186
+
187
+ # Save to file
188
+ timestamp = datetime.now(UTC).strftime("%Y%m%d-%H%M%S-%f")
189
+ safe_name = name.replace("/", "_").replace(":", "_")
190
+ filename = f"{safe_name}-{timestamp}.png"
191
+ image_path = self.baseline_dir / "current" / filename
192
+ image_path.parent.mkdir(parents=True, exist_ok=True)
193
+ image_path.write_bytes(screenshot_bytes)
194
+
195
+ # Convert to base64
196
+ image_base64 = base64.b64encode(screenshot_bytes).decode()
197
+
198
+ # Get dimensions
199
+ if selector:
200
+ box = await element.bounding_box()
201
+ dimensions = (
202
+ {"width": box["width"], "height": box["height"]}
203
+ if box
204
+ else {"width": 0, "height": 0}
205
+ )
206
+ else:
207
+ dimensions = await page.evaluate(
208
+ "() => ({ width: document.documentElement.scrollWidth, height: document.documentElement.scrollHeight })"
209
+ )
210
+
211
+ return ScreenshotResult(
212
+ url=url,
213
+ selector=selector,
214
+ timestamp=datetime.now(UTC).isoformat(),
215
+ image_base64=image_base64,
216
+ image_path=str(image_path),
217
+ dimensions=dimensions,
218
+ file_size=len(screenshot_bytes),
219
+ config=config,
220
+ )
221
+
222
+ def _generate_name(self, url: str, selector: str = None) -> str:
223
+ """Generate a safe name from URL and selector."""
224
+ from urllib.parse import urlparse
225
+
226
+ parsed = urlparse(url)
227
+ name = parsed.netloc + parsed.path.replace("/", "-")
228
+ if selector:
229
+ name += f"-{selector.replace(' ', '-').replace('.', '-').replace('#', '-')}"
230
+ return name[:100]
231
+
232
+ async def save_baseline(self, name: str, image_base64: str) -> str:
233
+ """Save a screenshot as baseline."""
234
+ baseline_path = self.baseline_dir / "baselines" / f"{name}.png"
235
+ baseline_path.parent.mkdir(parents=True, exist_ok=True)
236
+
237
+ image_bytes = base64.b64decode(image_base64)
238
+ baseline_path.write_bytes(image_bytes)
239
+
240
+ # Also save metadata
241
+ meta_path = baseline_path.with_suffix(".json")
242
+ import json
243
+
244
+ meta_path.write_text(
245
+ json.dumps(
246
+ {
247
+ "name": name,
248
+ "created_at": datetime.now(UTC).isoformat(),
249
+ "hash": hashlib.sha256(image_bytes).hexdigest(),
250
+ }
251
+ )
252
+ )
253
+
254
+ return str(baseline_path)
255
+
256
+ async def load_baseline(self, name: str) -> str | None:
257
+ """Load a baseline screenshot as base64."""
258
+ baseline_path = self.baseline_dir / "baselines" / f"{name}.png"
259
+ if not baseline_path.exists():
260
+ return None
261
+
262
+ image_bytes = baseline_path.read_bytes()
263
+ return base64.b64encode(image_bytes).decode()
264
+
265
+ async def compare(
266
+ self,
267
+ name: str,
268
+ current_base64: str,
269
+ threshold: float = None,
270
+ ) -> VisualDiffResult:
271
+ """
272
+ Compare current screenshot with baseline.
273
+
274
+ Args:
275
+ name: Baseline name
276
+ current_base64: Current screenshot as base64
277
+ threshold: Difference threshold (0-1), overrides default
278
+
279
+ Returns:
280
+ VisualDiffResult with comparison details
281
+ """
282
+ threshold = threshold or self.threshold
283
+ baseline_b64 = await self.load_baseline(name)
284
+
285
+ if not baseline_b64:
286
+ return VisualDiffResult(
287
+ baseline_path="",
288
+ current_path="",
289
+ diff_path=None,
290
+ passed=False,
291
+ difference_percent=100.0,
292
+ threshold_percent=threshold * 100,
293
+ mismatch_pixels=0,
294
+ total_pixels=0,
295
+ )
296
+
297
+ baseline_bytes = base64.b64decode(baseline_b64)
298
+ current_bytes = base64.b64decode(current_base64)
299
+
300
+ # Compare using pixelmatch or PIL
301
+ pixelmatch = await self._get_pixelmatch()
302
+
303
+ if pixelmatch == "PIL":
304
+ return await self._compare_pil(baseline_bytes, current_bytes, threshold, name)
305
+ elif pixelmatch:
306
+ return await self._compare_pixelmatch(
307
+ baseline_bytes, current_bytes, threshold, name, pixelmatch
308
+ )
309
+ else:
310
+ # Fallback: simple hash comparison
311
+ return self._compare_hash(baseline_bytes, current_bytes, threshold, name)
312
+
313
+ async def _compare_pil(
314
+ self,
315
+ baseline_bytes: bytes,
316
+ current_bytes: bytes,
317
+ threshold: float,
318
+ name: str,
319
+ ) -> VisualDiffResult:
320
+ """Compare using PIL."""
321
+ import io
322
+
323
+ from PIL import Image, ImageChops # noqa: F401 — availability check
324
+
325
+ baseline_img = Image.open(io.BytesIO(baseline_bytes)).convert("RGBA")
326
+ current_img = Image.open(io.BytesIO(current_bytes)).convert("RGBA")
327
+
328
+ # Resize if needed
329
+ if baseline_img.size != current_img.size:
330
+ current_img = current_img.resize(baseline_img.size, Image.Resampling.LANCZOS)
331
+
332
+ # Calculate difference
333
+ diff = ImageChops.difference(baseline_img, current_img)
334
+ diff_pixels = sum(
335
+ 1 for pixel in diff.getdata() if pixel[3] > 0
336
+ ) # Alpha > 0 means different
337
+ total_pixels = baseline_img.width * baseline_img.height
338
+ difference_percent = diff_pixels / total_pixels
339
+
340
+ # Generate diff image
341
+ diff_path = None
342
+ if difference_percent > threshold:
343
+ diff_img = Image.new("RGBA", baseline_img.size, (255, 0, 0, 0))
344
+ for x in range(baseline_img.width):
345
+ for y in range(baseline_img.height):
346
+ b = baseline_img.getpixel((x, y))
347
+ c = current_img.getpixel((x, y))
348
+ if b != c:
349
+ diff_img.putpixel((x, y), (255, 0, 0, 255))
350
+
351
+ diff_path = self.baseline_dir / "diffs" / f"{name}-diff.png"
352
+ diff_path.parent.mkdir(parents=True, exist_ok=True)
353
+ diff_img.save(diff_path)
354
+ diff_path = str(diff_path)
355
+
356
+ return VisualDiffResult(
357
+ baseline_path=str(self.baseline_dir / "baselines" / f"{name}.png"),
358
+ current_path="",
359
+ diff_path=diff_path,
360
+ passed=difference_percent <= threshold,
361
+ difference_percent=difference_percent * 100,
362
+ threshold_percent=threshold * 100,
363
+ mismatch_pixels=diff_pixels,
364
+ total_pixels=total_pixels,
365
+ )
366
+
367
+ async def _compare_pixelmatch(
368
+ self,
369
+ baseline_bytes: bytes,
370
+ current_bytes: bytes,
371
+ threshold: float,
372
+ name: str,
373
+ pixelmatch_module: Any,
374
+ ) -> VisualDiffResult:
375
+ """Compare using pixelmatch."""
376
+ # pixelmatch requires PNG buffers
377
+ # This is a simplified version
378
+ return await self._compare_pil(baseline_bytes, current_bytes, threshold, name)
379
+
380
+ def _compare_hash(
381
+ self,
382
+ baseline_bytes: bytes,
383
+ current_bytes: bytes,
384
+ threshold: float,
385
+ name: str,
386
+ ) -> VisualDiffResult:
387
+ """Fallback hash comparison."""
388
+ baseline_hash = hashlib.sha256(baseline_bytes).hexdigest()
389
+ current_hash = hashlib.sha256(current_bytes).hexdigest()
390
+
391
+ passed = baseline_hash == current_hash
392
+ difference_percent = 0.0 if passed else 100.0
393
+
394
+ return VisualDiffResult(
395
+ baseline_path=str(self.baseline_dir / "baselines" / f"{name}.png"),
396
+ current_path="",
397
+ diff_path=None,
398
+ passed=passed,
399
+ difference_percent=difference_percent,
400
+ threshold_percent=threshold * 100,
401
+ mismatch_pixels=0,
402
+ total_pixels=0,
403
+ )
404
+
405
+ async def batch_capture(
406
+ self,
407
+ page: Any,
408
+ urls: list[str],
409
+ config: ScreenshotConfig = None,
410
+ ) -> list[ScreenshotResult]:
411
+ """Capture screenshots for multiple URLs."""
412
+ results = []
413
+ for i, url in enumerate(urls):
414
+ self.on_progress(f"📸 [{i + 1}/{len(urls)}] {url}")
415
+ result = await self.capture(page, url, config=config)
416
+ results.append(result)
417
+ return results
418
+
419
+ async def batch_compare(
420
+ self,
421
+ name_prefix: str,
422
+ current_results: list[ScreenshotResult],
423
+ threshold: float = None,
424
+ ) -> list[VisualDiffResult]:
425
+ """Compare multiple screenshots with baselines."""
426
+ diffs = []
427
+ for i, result in enumerate(current_results):
428
+ name = f"{name_prefix}-{i}"
429
+ diff = await self.compare(name, result.image_base64, threshold)
430
+ diffs.append(diff)
431
+ return diffs
432
+
433
+
434
+ class _AsyncVisualRegressionAgent:
435
+ """Async wrapper for visual regression testing (used by LiveTestRunnerV2Agent)."""
436
+
437
+ def __init__(self, root: Path, config: dict):
438
+ self.root = root
439
+ self.config = config
440
+ self.manager = ScreenshotManager(
441
+ baseline_dir=root / ".patchi" / "visual_baselines",
442
+ threshold=config.get("visual_threshold", 0.1),
443
+ )
444
+
445
+ async def run(
446
+ self, urls: list[str], base_url: str = None, update_baselines: bool = False
447
+ ) -> dict:
448
+ """Run visual regression test on URLs."""
449
+ from patchi.core.testing.live_v2.browser_pool import BrowserConfig, get_browser_pool
450
+
451
+ pool = await get_browser_pool(BrowserConfig(headless=True))
452
+ page = await pool.get_page()
453
+
454
+ try:
455
+ results = await self.manager.batch_capture(page, urls)
456
+
457
+ if update_baselines:
458
+ for i, result in enumerate(results):
459
+ await self.manager.save_baseline(f"test-{i}", result.image_base64)
460
+ return {"updated": len(results), "message": "Baselines updated"}
461
+
462
+ diffs = await self.manager.batch_compare("test", results)
463
+
464
+ passed = sum(1 for d in diffs if d.passed)
465
+ failed = len(diffs) - passed
466
+
467
+ return {
468
+ "total": len(diffs),
469
+ "passed": passed,
470
+ "failed": failed,
471
+ "diffs": [d.__dict__ for d in diffs],
472
+ }
473
+ finally:
474
+ await pool.release_page(page)