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,240 @@
1
+ """CICDGeneratorAgent — generates CI/CD configs and pre-commit hooks.
2
+
3
+ Covers §10.1.1-4:
4
+ - Pre-commit hook config (.pre-commit-config.yaml)
5
+ - GitHub Actions workflow
6
+ - GitLab CI template
7
+ - Branch protection gate suggestions
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from .base import (
13
+ AgentGroup,
14
+ AgentInput,
15
+ AgentResult,
16
+ AgentStatus,
17
+ BaseAgent,
18
+ Severity,
19
+ make_finding,
20
+ register,
21
+ )
22
+
23
+ _PRE_COMMIT_CONFIG = """# Auto-generated by Patchi — pre-commit hooks for instant feedback
24
+ repos:
25
+ - repo: https://github.com/pre-commit/pre-commit-hooks
26
+ rev: v4.6.0
27
+ hooks:
28
+ - id: trailing-whitespace
29
+ - id: end-of-file-fixer
30
+ - id: check-yaml
31
+ - id: check-json
32
+ - id: check-added-large-files
33
+ - id: detect-private-key
34
+
35
+ - repo: local
36
+ hooks:
37
+ - id: patchi-sast
38
+ name: Patchi fast SAST (Bandit + Semgrep)
39
+ entry: python -m patchi.core.security.sast_gate
40
+ language: system
41
+ types: [python]
42
+ pass_filenames: true
43
+ - id: patchi-scan
44
+ name: Patchi scan
45
+ entry: patchi scan --staged
46
+ language: system
47
+ pass_filenames: false
48
+ """
49
+
50
+ _GITHUB_ACTIONS_WORKFLOW = """# Auto-generated by Patchi — CI workflow with explicit SAST tools
51
+ name: Patchi Security
52
+ on:
53
+ push:
54
+ branches: [main, develop]
55
+ pull_request:
56
+ branches: [main]
57
+
58
+ jobs:
59
+ sast:
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - uses: actions/checkout@v4
63
+ with:
64
+ fetch-depth: 0
65
+
66
+ - uses: actions/setup-python@v5
67
+ with:
68
+ python-version: "3.11"
69
+
70
+ - name: Install SAST tools
71
+ run: pip install bandit semgrep
72
+
73
+ - name: Bandit (SARIF)
74
+ run: bandit -r . -f sarif -o bandit.sarif || true
75
+
76
+ - name: Semgrep (SARIF)
77
+ run: semgrep --sarif --config auto --output semgrep.sarif || true
78
+
79
+ - name: Patchi consensus scan
80
+ run: patchi scan --sarif .patchi/output.sarif || true
81
+
82
+ - name: Upload Bandit SARIF
83
+ uses: github/codeql-action/upload-sarif@v3
84
+ with:
85
+ sarif_file: bandit.sarif
86
+ category: bandit
87
+
88
+ - name: Upload Semgrep SARIF
89
+ uses: github/codeql-action/upload-sarif@v3
90
+ with:
91
+ sarif_file: semgrep.sarif
92
+ category: semgrep
93
+
94
+ - name: Upload Patchi SARIF
95
+ uses: github/codeql-action/upload-sarif@v3
96
+ with:
97
+ sarif_file: .patchi/output.sarif
98
+ category: patchi
99
+
100
+ codeql:
101
+ runs-on: ubuntu-latest
102
+ permissions:
103
+ security-events: write
104
+ steps:
105
+ - uses: actions/checkout@v4
106
+ - name: Initialize CodeQL
107
+ uses: github/codeql-action/init@v3
108
+ with:
109
+ languages: python
110
+ - name: Perform CodeQL Analysis
111
+ uses: github/codeql-action/analyze@v3
112
+ """
113
+
114
+ _GITLAB_CI_TEMPLATE = """# Auto-generated by Patchi — GitLab CI template with explicit SAST tools
115
+ stages:
116
+ - analyze
117
+
118
+ bandit:
119
+ stage: analyze
120
+ image: python:3.11-slim
121
+ before_script:
122
+ - pip install bandit
123
+ script:
124
+ - bandit -r . -f json -o bandit.json || true
125
+ artifacts:
126
+ reports:
127
+ sast: bandit.json
128
+
129
+ semgrep:
130
+ stage: analyze
131
+ image: python:3.11-slim
132
+ before_script:
133
+ - pip install semgrep
134
+ script:
135
+ - semgrep --json --config auto --output semgrep.json || true
136
+ artifacts:
137
+ reports:
138
+ sast: semgrep.json
139
+
140
+ patchi-scan:
141
+ stage: analyze
142
+ image: python:3.11-slim
143
+ before_script:
144
+ - pip install patchi
145
+ script:
146
+ - patchi scan
147
+ artifacts:
148
+ reports:
149
+ code_quality: .patchi/output.sarif
150
+ """
151
+
152
+
153
+ @register
154
+ class CICDGeneratorAgent(BaseAgent):
155
+ """Generates CI/CD configs: pre-commit hooks, GitHub Actions, GitLab CI, branch protection."""
156
+
157
+ group = AgentGroup.GUARD
158
+ name = "CICDGeneratorAgent"
159
+ description = (
160
+ "Generate .pre-commit-config.yaml, GitHub Actions, GitLab CI, and branch protection gates"
161
+ )
162
+
163
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
164
+ existing_precommit = inp.root / ".pre-commit-config.yaml"
165
+ existing_github = inp.root / ".github" / "workflows" / "patchi-ci.yml"
166
+ existing_gitlab = inp.root / ".gitlab-ci.yml"
167
+
168
+ result.data["generated"] = []
169
+
170
+ if not existing_precommit.exists():
171
+ result.data["generated"].append(".pre-commit-config.yaml")
172
+ result.findings.append(
173
+ make_finding(
174
+ self.name,
175
+ "precommit_config",
176
+ Severity.INFO,
177
+ ".pre-commit-config.yaml",
178
+ "Generate pre-commit hook config for instant feedback",
179
+ detail=_PRE_COMMIT_CONFIG[:200],
180
+ suggestion="Run `patchi fix` to write this file",
181
+ )
182
+ )
183
+
184
+ if not existing_github.exists():
185
+ result.data["generated"].append(".github/workflows/patchi-ci.yml")
186
+ result.findings.append(
187
+ make_finding(
188
+ self.name,
189
+ "github_actions_workflow",
190
+ Severity.INFO,
191
+ ".github/workflows/patchi-ci.yml",
192
+ "Generate GitHub Actions workflow for CI",
193
+ detail=_GITHUB_ACTIONS_WORKFLOW[:200],
194
+ suggestion="Run `patchi fix` to write this file",
195
+ )
196
+ )
197
+
198
+ if not existing_gitlab.exists():
199
+ result.data["generated"].append(".gitlab-ci.yml")
200
+ result.findings.append(
201
+ make_finding(
202
+ self.name,
203
+ "gitlab_ci_template",
204
+ Severity.INFO,
205
+ ".gitlab-ci.yml",
206
+ "Generate GitLab CI template for CI",
207
+ detail=_GITLAB_CI_TEMPLATE[:200],
208
+ suggestion="Run `patchi fix` to write this file",
209
+ )
210
+ )
211
+
212
+ # Branch protection gates check
213
+ has_branch_protection = False
214
+ for conf in ["CODEOWNERS", ".github/CODEOWNERS", "CODEOWNERS.txt"]:
215
+ if (inp.root / conf).exists():
216
+ has_branch_protection = True
217
+ break
218
+
219
+ if not has_branch_protection:
220
+ result.findings.append(
221
+ make_finding(
222
+ self.name,
223
+ "branch_protection",
224
+ Severity.MEDIUM,
225
+ "",
226
+ "No branch protection or CODEOWNERS detected",
227
+ detail="Set up branch protection rules requiring Patchi to pass before merge",
228
+ suggestion="Configure branch protection in GitHub/GitLab settings",
229
+ )
230
+ )
231
+
232
+ result.data["templates"] = {
233
+ "precommit": _PRE_COMMIT_CONFIG,
234
+ "github_actions": _GITHUB_ACTIONS_WORKFLOW,
235
+ "gitlab_ci": _GITLAB_CI_TEMPLATE,
236
+ }
237
+
238
+ result.files_scanned = 3
239
+ if result.status == AgentStatus.RUNNING:
240
+ result.status = AgentStatus.DONE
@@ -0,0 +1,280 @@
1
+ """
2
+ CommentScanner — TODO/FIXME/HACK technical debt markers.
3
+
4
+ Scans for technical debt markers in comments:
5
+ - TODO: Planned features or improvements
6
+ - FIXME: Known issues that need fixing
7
+ - HACK: Workarounds that should be addressed
8
+ - XXX: Critical issues requiring attention
9
+ - NOSONAR: SonarQube suppression markers
10
+ - TEMP: Temporary code that needs removal
11
+ - KLUDGE: Crude solutions that need refinement
12
+ - BUG: Known bug markers
13
+ - REVIEW: Code requiring review
14
+ - OPTIMIZE: Performance improvement opportunities
15
+
16
+ Does NOT call AI.
17
+ Does NOT write to disk.
18
+ Does NOT touch the queue.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import re
24
+ from pathlib import Path
25
+
26
+ from ..brain.languages import DEFAULT_IGNORE_DIRS, Lang, detect_language
27
+ from .base import (
28
+ AgentGroup,
29
+ AgentInput,
30
+ AgentResult,
31
+ AgentStatus,
32
+ BaseAgent,
33
+ Finding,
34
+ Severity,
35
+ make_finding,
36
+ register,
37
+ safe_rglob,
38
+ scope_allows,
39
+ )
40
+
41
+
42
+ @register
43
+ class CommentScanner(BaseAgent):
44
+ """Scanner for technical debt markers in comments."""
45
+
46
+ group = AgentGroup.SCANNER
47
+ name = "CommentScanner"
48
+ description = "TODO/FIXME/HACK technical debt markers"
49
+
50
+ # Technical debt patterns with severity levels.
51
+ # Comment markers are hygiene, never vulnerabilities: capped at MEDIUM
52
+ # (a "BUG" comment was flagging CRITICAL and drowning the gate).
53
+ TECH_DEBT_PATTERNS = [
54
+ (r"XXX\b", "XXX", Severity.MEDIUM),
55
+ (r"BUG\b", "BUG", Severity.MEDIUM),
56
+ (r"HACK\b", "HACK", Severity.MEDIUM),
57
+ # Important issues
58
+ (r"FIXME\b", "FIXME", Severity.MEDIUM),
59
+ (r"TODO\b", "TODO", Severity.INFO),
60
+ # Less urgent but still noteworthy
61
+ (r"REVIEW\b", "REVIEW", Severity.LOW),
62
+ (r"KLUDGE\b", "KLUDGE", Severity.MEDIUM),
63
+ (r"TEMP\b", "TEMPORARY", Severity.LOW),
64
+ (r"OPTIMIZE\b", "OPTIMIZE", Severity.LOW),
65
+ (r"NOSONAR\b", "NOSONAR", Severity.LOW), # SonarQube suppression
66
+ ]
67
+
68
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
69
+ """Scan for technical debt markers in comments."""
70
+ findings = []
71
+
72
+ # Define file patterns for all supported languages
73
+ patterns = [
74
+ "*.js",
75
+ "*.jsx",
76
+ "*.ts",
77
+ "*.tsx",
78
+ "*.py",
79
+ "*.java",
80
+ "*.php",
81
+ "*.rb",
82
+ "*.go",
83
+ "*.rs",
84
+ "*.cpp",
85
+ "*.cxx",
86
+ "*.cc",
87
+ "*.c",
88
+ "*.h",
89
+ "*.hpp",
90
+ "*.cs",
91
+ "*.scala",
92
+ "*.kt",
93
+ "*.swift",
94
+ "*.dart",
95
+ ]
96
+
97
+ # Search for files
98
+ for pattern in patterns:
99
+ for file_path in safe_rglob(inp.root, pattern):
100
+ if file_path.is_file():
101
+ rel_path = file_path.relative_to(inp.root).as_posix()
102
+ if not scope_allows(inp, rel_path):
103
+ continue
104
+ if not self._should_skip_file(rel_path, inp):
105
+ findings.extend(self._scan_file_comments(file_path, rel_path))
106
+
107
+ # Add summary when technical debt markers exist
108
+ debt_findings = [
109
+ f
110
+ for f in findings
111
+ if any(marker_name in f.title for _, marker_name, _ in self.TECH_DEBT_PATTERNS)
112
+ ]
113
+ if debt_findings:
114
+ findings.append(
115
+ make_finding(
116
+ severity=Severity.INFO,
117
+ file="__summary__",
118
+ line_start=0,
119
+ title=f"Technical Debt Markers Found: {len(debt_findings)}",
120
+ description=f"Discovered {len(debt_findings)} technical debt markers in project",
121
+ evidence=f"Markers found in {len({f.file for f in debt_findings})} files",
122
+ )
123
+ )
124
+
125
+ by_type: dict[str, int] = {}
126
+ for finding in debt_findings:
127
+ match = re.search(r"Technical Debt Marker: (\w+)", finding.title)
128
+ if match:
129
+ marker = match.group(1)
130
+ by_type[marker] = by_type.get(marker, 0) + 1
131
+
132
+ result.status = AgentStatus.SUCCEEDED
133
+ result.findings = findings
134
+ result.data.update(
135
+ {
136
+ "todo_count": len(
137
+ [
138
+ f
139
+ for f in findings
140
+ if f.type == "technical_debt" and "TODO" in f.message.upper()
141
+ ]
142
+ ),
143
+ "fixme_count": len(
144
+ [
145
+ f
146
+ for f in findings
147
+ if f.type == "technical_debt" and "FIXME" in f.message.upper()
148
+ ]
149
+ ),
150
+ "hack_count": len(
151
+ [
152
+ f
153
+ for f in findings
154
+ if f.type == "technical_debt" and "HACK" in f.message.upper()
155
+ ]
156
+ ),
157
+ "bug_count": len(
158
+ [
159
+ f
160
+ for f in findings
161
+ if f.type == "technical_debt" and "BUG" in f.message.upper()
162
+ ]
163
+ ),
164
+ "total_markers": len(debt_findings),
165
+ "by_type": by_type,
166
+ "needs_ai": False,
167
+ }
168
+ )
169
+ return
170
+
171
+ def _should_skip_file(self, file_path: str, inp: AgentInput) -> bool:
172
+ """Check if file should be skipped based on restrictions."""
173
+ # Skip ignored directories (node_modules, .venv, etc.)
174
+ from pathlib import PurePosixPath
175
+
176
+ if any(p in DEFAULT_IGNORE_DIRS for p in PurePosixPath(file_path).parts):
177
+ return True
178
+
179
+ from pathlib import PurePosixPath
180
+
181
+ # Check restrictions
182
+ restrictions = inp.config.get("restrictions", [])
183
+ for r in restrictions:
184
+ if r.get("enabled", True):
185
+ path = r["path"]
186
+ if file_path.startswith(path) or PurePosixPath(file_path).match(path):
187
+ if r["type"] == "NO_TOUCH":
188
+ return True
189
+ elif r["type"] == "SCAN_ONLY" and self.__class__.__name__ == "FixAgent":
190
+ return True
191
+ return False
192
+
193
+ def _scan_file_comments(self, file_path: Path, rel_path: str) -> list[Finding]:
194
+ """Scan a file for technical debt markers in comments."""
195
+ findings = []
196
+
197
+ try:
198
+ content = file_path.read_text(encoding="utf-8")
199
+ lines = content.splitlines()
200
+
201
+ # Different comment patterns for different languages
202
+ lang = detect_language(file_path)
203
+ comment_patterns = self._get_comment_patterns(lang)
204
+
205
+ for line_num, line in enumerate(lines, 1):
206
+ # Find all comment sections in the line
207
+ for comment_pattern in comment_patterns:
208
+ comment_matches = re.finditer(comment_pattern, line)
209
+ for comment_match in comment_matches:
210
+ comment_text = comment_match.group(1) # The content of the comment
211
+
212
+ # Look for technical debt markers in the comment
213
+ for pattern, marker_name, severity in self.TECH_DEBT_PATTERNS:
214
+ debt_matches = re.finditer(pattern, comment_text, re.IGNORECASE)
215
+ for debt_match in debt_matches:
216
+ # Extract context around the marker
217
+ start_idx = max(0, debt_match.start() - 20)
218
+ end_idx = min(len(comment_text), debt_match.end() + 40)
219
+ context = comment_text[start_idx:end_idx]
220
+
221
+ findings.append(
222
+ make_finding(
223
+ severity=severity,
224
+ finding_type="technical_debt",
225
+ file=rel_path,
226
+ line_start=line_num,
227
+ title=f"Technical Debt Marker: {marker_name}",
228
+ description=f"Found {marker_name} marker in comment",
229
+ evidence=f"Context: {context.strip()}",
230
+ )
231
+ )
232
+
233
+ except Exception as e:
234
+ findings.append(
235
+ make_finding(
236
+ severity=Severity.LOW,
237
+ file=rel_path,
238
+ line_start=0,
239
+ title="Comment file read error",
240
+ description=f"Could not read file {file_path.name} for comment scanning: {str(e)}",
241
+ evidence=str(e),
242
+ )
243
+ )
244
+
245
+ return findings
246
+
247
+ def _get_comment_patterns(self, lang: Lang) -> list[str]:
248
+ """Get appropriate comment patterns for the language."""
249
+ # Use a broad set of comment capture patterns for most languages.
250
+ return [
251
+ r"#(.*)$", # Hash-style comment
252
+ r"//(.*)$", # Double-slash comment
253
+ r"/\*(.*?)\*/", # C-style multi-line comment
254
+ ]
255
+
256
+ def _get_language_extensions(self) -> dict[str, Lang]:
257
+ """Map file extensions to language enums."""
258
+ return {
259
+ ".js": Lang.JAVASCRIPT,
260
+ ".jsx": Lang.JAVASCRIPT,
261
+ ".ts": Lang.TYPESCRIPT,
262
+ ".tsx": Lang.TYPESCRIPT,
263
+ ".py": Lang.PYTHON,
264
+ ".java": Lang.JAVA,
265
+ ".php": Lang.PHP,
266
+ ".rb": Lang.RUBY,
267
+ ".go": Lang.GO,
268
+ ".rs": Lang.RUST,
269
+ ".cpp": Lang.CPP,
270
+ ".cxx": Lang.CPP,
271
+ ".cc": Lang.CPP,
272
+ ".c": Lang.C,
273
+ ".h": Lang.C,
274
+ ".hpp": Lang.CPP,
275
+ ".cs": Lang.C_SHARP,
276
+ ".scala": Lang.SCALA,
277
+ ".kt": Lang.KOTLIN,
278
+ ".swift": Lang.SWIFT,
279
+ ".dart": Lang.DART,
280
+ }
@@ -0,0 +1,156 @@
1
+ """ConsoleLoggingAgent §p test — background console/pageerror capture.
2
+
3
+ Runs automatically when `p test` is active (Group.TEST), never under `p scan`.
4
+ Attaches Playwright console + pageerror listeners on every discovered route
5
+ (via shared open_page helper) and tails the app launcher log, so JS runtime
6
+ noise is captured even on pages other testers skip. Findings are console-error
7
+ clusters; raw traces go to result.data for brain/verify.py merging.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from collections import Counter
14
+
15
+ from patchi.core.agents.base import (
16
+ AgentGroup,
17
+ AgentInput,
18
+ AgentResult,
19
+ AgentStatus,
20
+ BaseAgent,
21
+ Severity,
22
+ make_finding,
23
+ register,
24
+ )
25
+
26
+ _log = logging.getLogger("patchi.agents.console_logging")
27
+
28
+
29
+ @register
30
+ class ConsoleLoggingAgent(BaseAgent):
31
+ group = AgentGroup.TEST
32
+ name = "ConsoleLoggingAgent"
33
+ description = "Background console/pageerror capture across routes + app log tail"
34
+ timeout = 60
35
+
36
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
37
+ try:
38
+ from playwright.sync_api import sync_playwright # noqa: F401
39
+ except ImportError:
40
+ self.skip(result, "playwright not installed — pip install playwright")
41
+ return
42
+
43
+ from patchi.core.testing._browser import discover_routes, find_server, open_page
44
+
45
+ base_url = find_server(inp.root, inp.config, inp.extra)
46
+ if not base_url:
47
+ self.skip(result, "no running server found — start `p web` (default :1612)")
48
+ return
49
+
50
+ routes = discover_routes(inp.config, inp.extra)
51
+ if not routes:
52
+ self.skip(result, "no routes discovered")
53
+ return
54
+
55
+ from patchi.core.brain.trace_log import trace_agent
56
+
57
+ console_errors: list[str] = []
58
+ page_errors: list[str] = []
59
+ pages_tested = 0
60
+ error_pages = 0
61
+
62
+ with trace_agent(self.name, root=inp.root) as trace:
63
+ with sync_playwright() as pw:
64
+ browser = pw.chromium.launch(headless=True)
65
+ try:
66
+ for page_path in routes[:15]:
67
+ ps = open_page(browser, f"{base_url}{page_path}")
68
+ try:
69
+ if ps.status and ps.status >= 400:
70
+ error_pages += 1
71
+ continue
72
+ for ce in ps.console_errors:
73
+ console_errors.append(f"{page_path} :: {ce[:200]}")
74
+ for pe in ps.page_errors:
75
+ page_errors.append(f"{page_path} :: {pe[:200]}")
76
+ pages_tested += 1
77
+ finally:
78
+ ps.page.close()
79
+ finally:
80
+ browser.close()
81
+
82
+ # Tail the app launcher log for server-side noise
83
+ app_log_lines: list[str] = []
84
+ log_path = inp.root / ".patchi" / "launcher" / "app.log"
85
+ try:
86
+ if log_path.exists():
87
+ text = log_path.read_text(encoding="utf-8", errors="replace")
88
+ app_log_lines = [ln for ln in text.splitlines() if ln.strip()][-50:]
89
+ except OSError as exc:
90
+ _log.debug("app log tail failed: %s", exc)
91
+
92
+ trace.files_scanned = pages_tested
93
+
94
+ # Cluster console errors so one noisy library = one finding
95
+ clusters = Counter()
96
+ for entry in console_errors:
97
+ route, _, msg = entry.partition(" :: ")
98
+ key = (route, msg[:80])
99
+ clusters[key] += 1
100
+ for (route, msg), count in sorted(clusters.items(), key=lambda kv: -kv[1])[:10]:
101
+ text = msg[:160].strip() or "empty error text"
102
+ result.add_finding(
103
+ make_finding(
104
+ agent=self.name,
105
+ finding_type="console_error",
106
+ severity=Severity.LOW if count == 1 else Severity.MEDIUM,
107
+ file=route,
108
+ message=f"Console error ×{count} on {route}: {text}",
109
+ )
110
+ )
111
+ for entry in page_errors[:10]:
112
+ route, _, msg = entry.partition(" :: ")
113
+ text = msg[:160].strip() or "empty error text"
114
+ result.add_finding(
115
+ make_finding(
116
+ agent=self.name,
117
+ finding_type="page_error",
118
+ severity=Severity.MEDIUM,
119
+ file=route,
120
+ message=f"Uncaught page error on {route}: {text}",
121
+ )
122
+ )
123
+ if error_pages:
124
+ result.add_finding(
125
+ make_finding(
126
+ agent=self.name,
127
+ finding_type="console_error_pages",
128
+ severity=Severity.HIGH,
129
+ file="(all pages)",
130
+ message=f"{error_pages} page(s) HTTP ≥400 — console capture skipped",
131
+ )
132
+ )
133
+ if not console_errors and not page_errors and pages_tested > 0:
134
+ result.add_finding(
135
+ make_finding(
136
+ agent=self.name,
137
+ finding_type="console_summary",
138
+ severity=Severity.INFO,
139
+ file="(all pages)",
140
+ message=f"Console clean: {pages_tested} pages, 0 console/page errors",
141
+ )
142
+ )
143
+
144
+ result.data["suite"] = {
145
+ "runner": "console_logging",
146
+ "pages_tested": pages_tested,
147
+ "console_errors": console_errors[:10],
148
+ "page_errors": page_errors[:10],
149
+ "error_pages": error_pages,
150
+ "app_log_tail": app_log_lines[-10:],
151
+ }
152
+ result.files_scanned = pages_tested
153
+ trace.findings = len(result.findings)
154
+
155
+ if result.status == AgentStatus.RUNNING:
156
+ result.status = AgentStatus.DONE