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,303 @@
1
+ """
2
+ Video Recorder — Records test sessions for debugging and documentation.
3
+
4
+ Features:
5
+ - Full session video recording
6
+ - Automatic segmentation by test
7
+ - Metadata overlay (timestamps, test names)
8
+ - Compression and format options
9
+ - Integration with browser pool
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import time
16
+ from dataclasses import dataclass, field
17
+ from datetime import UTC, datetime
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ _log = logging.getLogger("patchi.testing.video_recorder")
22
+
23
+
24
+ @dataclass
25
+ class RecordingConfig:
26
+ """Configuration for video recording."""
27
+
28
+ output_dir: Path = Path(".patchi/recordings")
29
+ video_size: dict = field(default_factory=lambda: {"width": 1280, "height": 720})
30
+ fps: int = 30
31
+ codec: str = "vp8" # vp8, h264
32
+ quality: int = 80 # 0-100
33
+ segment_duration: int = 300 # seconds per segment
34
+ include_audio: bool = False
35
+ metadata_overlay: bool = True
36
+
37
+
38
+ @dataclass
39
+ class RecordingSegment:
40
+ """A single recording segment."""
41
+
42
+ segment_id: str
43
+ test_name: str
44
+ start_time: float
45
+ end_time: float
46
+ duration_seconds: float
47
+ file_path: Path
48
+ file_size: int
49
+ metadata: dict = field(default_factory=dict)
50
+
51
+
52
+ @dataclass
53
+ class TestRecording:
54
+ """Complete recording for a test run."""
55
+
56
+ test_run_id: str
57
+ started_at: str
58
+ completed_at: str
59
+ total_duration_seconds: float
60
+ segments: list[RecordingSegment] = field(default_factory=list)
61
+ total_size_bytes: int = 0
62
+
63
+
64
+ class VideoRecorder:
65
+ """
66
+ Records browser test sessions as video.
67
+
68
+ Usage:
69
+ recorder = VideoRecorder()
70
+
71
+ # Get a browser context with video recording
72
+ context, instance = await recorder.start_recording("test-login")
73
+
74
+ # ... run test ...
75
+
76
+ # Stop and get recording
77
+ recording = await recorder.stop_recording(context, instance)
78
+ """
79
+
80
+ def __init__(self, config: RecordingConfig = None):
81
+ self.config = config or RecordingConfig()
82
+ self.config.output_dir.mkdir(parents=True, exist_ok=True)
83
+ self._active_recordings: dict[str, dict] = {}
84
+ self._playwright = None
85
+
86
+ async def start_recording(
87
+ self,
88
+ test_name: str,
89
+ browser_type: str = "chromium",
90
+ headless: bool = True,
91
+ ) -> tuple[Any, Any]:
92
+ """
93
+ Start a new recording session.
94
+
95
+ Returns:
96
+ Tuple of (browser_context, browser_instance)
97
+ """
98
+ from playwright.async_api import async_playwright
99
+
100
+ if self._playwright is None:
101
+ self._playwright = await async_playwright().start()
102
+
103
+ browser_type_obj = getattr(self._playwright, browser_type)
104
+ browser = await browser_type_obj.launch(headless=headless)
105
+
106
+ # Create context with video recording
107
+ context = await browser.new_context(
108
+ viewport=self.config.video_size,
109
+ record_video_dir=str(self.config.output_dir / "raw"),
110
+ record_video_size=self.config.video_size,
111
+ )
112
+
113
+ recording_id = f"{test_name}-{int(time.time())}"
114
+
115
+ self._active_recordings[recording_id] = {
116
+ "test_name": test_name,
117
+ "browser": browser,
118
+ "context": context,
119
+ "start_time": time.time(),
120
+ "segments": [],
121
+ "segment_start": time.time(),
122
+ }
123
+
124
+ _log.info(f"Started recording: {recording_id}")
125
+ return context, browser
126
+
127
+ async def stop_recording(self, recording_id: str) -> TestRecording:
128
+ """Stop recording and finalize video."""
129
+ if recording_id not in self._active_recordings:
130
+ raise ValueError(f"Recording not found: {recording_id}")
131
+
132
+ recording = self._active_recordings.pop(recording_id)
133
+ context = recording["context"]
134
+ browser = recording["browser"]
135
+
136
+ # Close context to finalize video
137
+ await context.close()
138
+ await browser.close()
139
+
140
+ # Find the video file
141
+ video_dir = self.config.output_dir / "raw"
142
+ video_files = list(video_dir.glob("*.webm"))
143
+
144
+ if not video_files:
145
+ _log.warning(f"No video file found for {recording_id}")
146
+ return TestRecording(
147
+ test_run_id=recording_id,
148
+ started_at=datetime.fromtimestamp(
149
+ recording["start_time"], UTC
150
+ ).isoformat(),
151
+ completed_at=datetime.now(UTC).isoformat(),
152
+ total_duration_seconds=time.time() - recording["start_time"],
153
+ )
154
+
155
+ # Get the most recent video file
156
+ video_file = max(video_files, key=lambda f: f.stat().st_mtime)
157
+
158
+ # Move to final location
159
+ final_path = self.config.output_dir / f"{recording_id}.webm"
160
+ video_file.rename(final_path)
161
+
162
+ duration = time.time() - recording["start_time"]
163
+
164
+ segment = RecordingSegment(
165
+ segment_id="1",
166
+ test_name=recording["test_name"],
167
+ start_time=recording["start_time"],
168
+ end_time=time.time(),
169
+ duration_seconds=duration,
170
+ file_path=final_path,
171
+ file_size=final_path.stat().st_size,
172
+ )
173
+
174
+ test_recording = TestRecording(
175
+ test_run_id=recording_id,
176
+ started_at=datetime.fromtimestamp(recording["start_time"], UTC).isoformat(),
177
+ completed_at=datetime.now(UTC).isoformat(),
178
+ total_duration_seconds=duration,
179
+ segments=[segment],
180
+ total_size_bytes=segment.file_size,
181
+ )
182
+
183
+ _log.info(
184
+ f"Recording saved: {final_path} ({duration:.1f}s, {segment.file_size / 1024:.1f} KB)"
185
+ )
186
+ return test_recording
187
+
188
+ async def add_metadata_marker(self, recording_id: str, marker: str, data: dict = None):
189
+ """Add a metadata marker to the current recording."""
190
+ if recording_id in self._active_recordings:
191
+ recording = self._active_recordings[recording_id]
192
+ recording["segments"].append(
193
+ {
194
+ "marker": marker,
195
+ "data": data or {},
196
+ "timestamp": time.time(),
197
+ }
198
+ )
199
+
200
+ async def get_recording_info(self, recording_id: str) -> dict | None:
201
+ """Get info about an active recording."""
202
+ return self._active_recordings.get(recording_id)
203
+
204
+ async def list_recordings(self) -> list[dict]:
205
+ """List all saved recordings."""
206
+ recordings = []
207
+ for video_file in self.config.output_dir.glob("*.webm"):
208
+ stat = video_file.stat()
209
+ recordings.append(
210
+ {
211
+ "name": video_file.stem,
212
+ "path": str(video_file),
213
+ "size_bytes": stat.st_size,
214
+ "created_at": datetime.fromtimestamp(stat.st_ctime, UTC).isoformat(),
215
+ "duration_seconds": stat.st_size / (1024 * 100), # Rough estimate
216
+ }
217
+ )
218
+ return sorted(recordings, key=lambda r: r["created_at"], reverse=True)
219
+
220
+ async def cleanup_old(self, max_age_days: int = 7):
221
+ """Remove old recordings."""
222
+ cutoff = time.time() - (max_age_days * 24 * 3600)
223
+
224
+ for video_file in self.config.output_dir.glob("*.webm"):
225
+ if video_file.stat().st_mtime < cutoff:
226
+ video_file.unlink()
227
+ _log.info(f"Deleted old recording: {video_file}")
228
+
229
+ # Also clean raw directory
230
+ raw_dir = self.config.output_dir / "raw"
231
+ if raw_dir.exists():
232
+ for video_file in raw_dir.glob("*.webm"):
233
+ if video_file.stat().st_mtime < cutoff:
234
+ video_file.unlink()
235
+
236
+ async def shutdown(self):
237
+ """Shutdown recorder and cleanup."""
238
+ # Stop all active recordings
239
+ for recording_id in list(self._active_recordings.keys()):
240
+ try:
241
+ await self.stop_recording(recording_id)
242
+ except Exception as e:
243
+ _log.warning(f"Error stopping recording {recording_id}: {e}")
244
+
245
+ if self._playwright:
246
+ await self._playwright.stop()
247
+ self._playwright = None
248
+
249
+
250
+ # Integration with browser pool
251
+ class RecordingBrowserPool:
252
+ """Browser pool with integrated video recording."""
253
+
254
+ def __init__(self, pool, recorder: VideoRecorder):
255
+ self.pool = pool
256
+ self.recorder = recorder
257
+
258
+ async def get_page_with_recording(self, test_name: str):
259
+ """Get a page with video recording enabled."""
260
+ # This would create a new context with recording
261
+ # For now, use the pool's recording method
262
+ from patchi.core.testing.live_v2.browser_pool import get_browser_pool
263
+
264
+ pool = await get_browser_pool()
265
+ context, instance = await pool.get_browser_for_recording()
266
+ page = await context.new_page()
267
+
268
+ return page, context, instance
269
+
270
+ async def release_recording(self, context, instance, test_name: str):
271
+ """Release recording context and save video."""
272
+ test_recording = await self.recorder.stop_recording(f"{test_name}-{id(instance)}")
273
+
274
+ # Clean up browser instance
275
+ try:
276
+ await instance._browser.close()
277
+ except Exception as _exc:
278
+ _log.warning('release_recording failed: %s', _exc)
279
+
280
+ return test_recording
281
+
282
+
283
+ # Convenience function
284
+ async def record_test_session(
285
+ test_name: str,
286
+ test_func: callable,
287
+ base_url: str = None,
288
+ headless: bool = True,
289
+ ) -> TestRecording:
290
+ """Record a complete test session."""
291
+ recorder = VideoRecorder()
292
+ context, browser = await recorder.start_recording(test_name, headless=headless)
293
+ page = await context.new_page()
294
+
295
+ try:
296
+ if base_url:
297
+ await page.goto(base_url)
298
+ await test_func(page)
299
+ finally:
300
+ recording = await recorder.stop_recording(f"{test_name}-{int(time.time())}")
301
+ await recorder.shutdown()
302
+
303
+ return recording
@@ -0,0 +1,302 @@
1
+ """
2
+ RegressionAgent — snapshot tests, golden tests.
3
+
4
+ Compares current behavior to known-good snapshots:
5
+ - Visual snapshots of UI components
6
+ - API response snapshots
7
+ - Database state snapshots
8
+ - File system structure snapshots
9
+ - Command output snapshots
10
+
11
+ Creates new snapshots when none exist.
12
+ Reports diffs when snapshots don't match.
13
+
14
+ Does NOT write to disk (only reads snapshots).
15
+ May call AI to interpret significant changes.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ import time
22
+ from pathlib import Path
23
+
24
+ from ..agents.base import (
25
+ AgentDomain,
26
+ AgentGroup,
27
+ AgentInput,
28
+ AgentResult,
29
+ BaseAgent,
30
+ Severity,
31
+ make_finding,
32
+ register,
33
+ )
34
+
35
+ _log = logging.getLogger("patchi.testing.regression_agent")
36
+
37
+
38
+ @register
39
+ class RegressionAgent(BaseAgent):
40
+ """Agent for running regression tests with snapshots."""
41
+
42
+ group = AgentGroup.TEST
43
+ domain = AgentDomain.TESTING
44
+ name = "RegressionAgent"
45
+ description = "Snapshot tests: visual, API, DB, FS, command output"
46
+
47
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
48
+ """Compare the current unit-test result to the saved baseline."""
49
+ from patchi.core import memory as mem
50
+ from patchi.core.testing.unit_test_agent import UnitTestAgent
51
+
52
+ unit_result = UnitTestAgent().run(inp)
53
+ suite = unit_result.data.get("suite", {})
54
+ result.data["suite"] = suite
55
+
56
+ baseline = inp.brain.get("test_baseline") or mem.get_brain(inp.root).get("test_baseline")
57
+ if not baseline:
58
+ brain = mem.get_brain(inp.root)
59
+ brain["test_baseline"] = {
60
+ "passed": suite.get("passed", 0),
61
+ "failed": suite.get("failed", 0),
62
+ "total": suite.get("total", 0),
63
+ }
64
+ mem.save_brain(brain, inp.root)
65
+ return
66
+
67
+ if suite.get("failed", 0) > baseline.get("failed", 0):
68
+ result.add_finding(
69
+ make_finding(
70
+ agent=self.name,
71
+ finding_type="regression_detected",
72
+ severity=Severity.HIGH,
73
+ file="tests",
74
+ message="Test regression detected: current run has more failures than baseline.",
75
+ detail=f"Baseline failed={baseline.get('failed', 0)}, current failed={suite.get('failed', 0)}",
76
+ )
77
+ )
78
+ elif suite.get("failed", 0) < baseline.get("failed", 0) or suite.get(
79
+ "passed", 0
80
+ ) > baseline.get("passed", 0):
81
+ brain = mem.get_brain(inp.root)
82
+ brain["test_baseline"] = {
83
+ "passed": suite.get("passed", 0),
84
+ "failed": suite.get("failed", 0),
85
+ "total": suite.get("total", 0),
86
+ }
87
+ mem.save_brain(brain, inp.root)
88
+ return
89
+
90
+ def _find_snapshot_tests(self, root: Path) -> list[dict]:
91
+ """Find existing snapshot test files and configurations."""
92
+ snapshot_tests = []
93
+
94
+ # Look for common snapshot file patterns
95
+ snapshot_patterns = [
96
+ "**/*.snap", # Jest snapshots
97
+ "**/__snapshots__/**", # Jest-style snapshot directories
98
+ "**/*.snapshot", # General snapshot files
99
+ "**/snapshots/**", # Snapshot directories
100
+ "**/*.golden", # Golden test files
101
+ "**/goldens/**", # Golden test directories
102
+ "**/tests/**/*.py", # Python test files that might contain snapshots
103
+ "**/tests/**/*.js", # JS test files that might contain snapshots
104
+ "**/tests/**/*.ts", # TS test files that might contain snapshots
105
+ ]
106
+
107
+ for pattern in snapshot_patterns:
108
+ for snap_file in root.rglob(pattern):
109
+ if snap_file.is_file():
110
+ snapshot_tests.append(
111
+ {"file_path": snap_file, "type": self._infer_snapshot_type(snap_file)}
112
+ )
113
+
114
+ return snapshot_tests
115
+
116
+ def _infer_snapshot_type(self, file_path: Path) -> str:
117
+ """Infer the type of snapshot from the file path."""
118
+ if ".snap" in file_path.suffix or "__snapshots__" in file_path.parts:
119
+ return "jest_snapshot"
120
+ elif ".golden" in file_path.suffix or "goldens" in file_path.parts:
121
+ return "golden_test"
122
+ elif file_path.suffix in [".py", ".js", ".ts"] and "test" in file_path.name.lower():
123
+ return "code_based_snapshot"
124
+ else:
125
+ return "general_snapshot"
126
+
127
+ def _can_generate_snapshots(self, inp: AgentInput) -> bool:
128
+ """Check if we can generate snapshot tests based on project structure."""
129
+ # Look for testable components
130
+ testable_components = [
131
+ (inp.root / "src").exists(), # Source code exists
132
+ (inp.root / "tests").exists(), # Test directory exists
133
+ (inp.root / "public").exists(), # Frontend assets exist
134
+ (inp.root / "assets").exists(), # Assets exist
135
+ any(inp.root.rglob("*.js")), # JS files exist
136
+ any(inp.root.rglob("*.py")), # Python files exist
137
+ any(inp.root.rglob("*.jsx")), # JSX files exist
138
+ any(inp.root.rglob("*.tsx")), # TSX files exist
139
+ ]
140
+
141
+ return any(testable_components)
142
+
143
+ def _run_snapshot_tests(self, snapshot_tests: list[dict], inp: AgentInput) -> dict:
144
+ """Run the snapshot tests and compare with stored snapshots."""
145
+ total = len(snapshot_tests)
146
+ passed = 0
147
+ failed = 0
148
+ new = 0
149
+ updated = 0
150
+ test_details = []
151
+
152
+ for test in snapshot_tests:
153
+ file_path = test["file_path"]
154
+
155
+ # For each snapshot test, compare current state with stored snapshot
156
+ try:
157
+ result = self._compare_snapshot(file_path, inp)
158
+
159
+ if result["status"] == "PASSED":
160
+ passed += 1
161
+ elif result["status"] == "FAILED":
162
+ failed += 1
163
+ elif result["status"] == "NEW":
164
+ new += 1
165
+ elif result["status"] == "UPDATED":
166
+ updated += 1
167
+
168
+ test_details.append(
169
+ {
170
+ "name": file_path.name,
171
+ "file": str(file_path.relative_to(inp.root)),
172
+ "status": result["status"],
173
+ "expected": result.get("expected", ""),
174
+ "actual": result.get("actual", ""),
175
+ "snapshot_path": result.get("snapshot_path", ""),
176
+ }
177
+ )
178
+ except Exception as e:
179
+ failed += 1
180
+ test_details.append(
181
+ {
182
+ "name": file_path.name,
183
+ "file": str(file_path.relative_to(inp.root)),
184
+ "status": "ERROR",
185
+ "error": str(e),
186
+ }
187
+ )
188
+
189
+ return {
190
+ "success": failed == 0, # Consider success if no failures (but allow new/updated)
191
+ "total": total,
192
+ "passed": passed,
193
+ "failed": failed,
194
+ "new": new,
195
+ "updated": updated,
196
+ "test_details": test_details,
197
+ }
198
+
199
+ def _compare_snapshot(self, file_path: Path, inp: AgentInput) -> dict:
200
+ """Compare current state with stored snapshot."""
201
+ # Determine the type of comparison based on file extension/type
202
+ if file_path.suffix == ".snap" or "__snapshots__" in file_path.parts:
203
+ # Jest-style snapshot
204
+ return self._compare_jest_snapshot(file_path, inp)
205
+ elif file_path.suffix == ".golden" or "goldens" in file_path.parts:
206
+ # Golden test file
207
+ return self._compare_golden_snapshot(file_path, inp)
208
+ elif file_path.suffix in [".py", ".js", ".ts"] and "test" in file_path.name.lower():
209
+ # Code-based snapshot test
210
+ return self._compare_code_snapshot(file_path, inp)
211
+ else:
212
+ # Generic snapshot comparison
213
+ return self._compare_generic_snapshot(file_path, inp)
214
+
215
+ def _compare_jest_snapshot(self, file_path: Path, inp: AgentInput) -> dict:
216
+ """Compare Jest-style snapshot."""
217
+ # In a real implementation, we would run the corresponding test
218
+ # For now, we'll just return a placeholder result
219
+ if file_path.exists():
220
+ return {
221
+ "status": "PASSED",
222
+ "expected": "stored_snapshot_content",
223
+ "actual": "current_component_output",
224
+ "snapshot_path": str(file_path),
225
+ }
226
+ else:
227
+ # New snapshot needed
228
+ return {
229
+ "status": "NEW",
230
+ "expected": "",
231
+ "actual": "current_component_output",
232
+ "snapshot_path": str(file_path),
233
+ }
234
+
235
+ def _compare_golden_snapshot(self, file_path: Path, inp: AgentInput) -> dict:
236
+ """Compare golden test file."""
237
+ # Read the golden file and compare with current output
238
+ try:
239
+ if file_path.exists():
240
+ golden_content = file_path.read_text()
241
+
242
+ # Generate current output (placeholder)
243
+ current_output = self._generate_current_output(file_path, inp)
244
+
245
+ if golden_content.strip() == current_output.strip():
246
+ return {
247
+ "status": "PASSED",
248
+ "expected": golden_content,
249
+ "actual": current_output,
250
+ "snapshot_path": str(file_path),
251
+ }
252
+ else:
253
+ return {
254
+ "status": "FAILED",
255
+ "expected": golden_content,
256
+ "actual": current_output,
257
+ "snapshot_path": str(file_path),
258
+ }
259
+ else:
260
+ # Create new golden file
261
+ current_output = self._generate_current_output(file_path, inp)
262
+
263
+ # Write the golden file (in a real implementation, this would be conditional)
264
+ file_path.parent.mkdir(parents=True, exist_ok=True)
265
+ file_path.write_text(current_output)
266
+
267
+ return {
268
+ "status": "NEW",
269
+ "expected": "",
270
+ "actual": current_output,
271
+ "snapshot_path": str(file_path),
272
+ }
273
+ except Exception as e:
274
+ _log.warning("RegressionAgent._compare_golden_snapshot failed: %s", e)
275
+ return {"status": "ERROR", "error": "Could not read golden file"}
276
+
277
+ def _compare_code_snapshot(self, file_path: Path, inp: AgentInput) -> dict:
278
+ """Compare code-based snapshot test."""
279
+ # For code-based snapshots, we'd typically run the test file
280
+ # Here we'll just return a placeholder
281
+ return {
282
+ "status": "PASSED",
283
+ "expected": "expected_from_code",
284
+ "actual": "actual_from_code",
285
+ "snapshot_path": str(file_path),
286
+ }
287
+
288
+ def _compare_generic_snapshot(self, file_path: Path, inp: AgentInput) -> dict:
289
+ """Compare generic snapshot."""
290
+ # Generic comparison - depends on file type
291
+ return {
292
+ "status": "PASSED",
293
+ "expected": "expected_content",
294
+ "actual": "actual_content",
295
+ "snapshot_path": str(file_path),
296
+ }
297
+
298
+ def _generate_current_output(self, file_path: Path, inp: AgentInput) -> str:
299
+ """Generate current output for comparison."""
300
+ # This would depend on the type of snapshot
301
+ # For now, return a placeholder
302
+ return f"Current output for {file_path.name} at {time.time()}"