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,102 @@
1
+ """
2
+ MemoryProfilerAgent §5.2 — Memory & Performance Profiling.
3
+
4
+ Covers:
5
+ 5.2.1 Memory Leak Detector (heap growth via CDP/memray/pprof)
6
+ 5.2.2 Detached DOM Detection (CDP heap snapshot)
7
+ 5.2.3 Event Listener Leak Tracker (add/remove imbalance)
8
+ 5.2.4 Bundle Size Regression (webpack/vite output sizes)
9
+ 5.2.5 Startup Time Benchmark (cold start)
10
+
11
+ All via subprocess heuristic fallback if CDP/memray not available — never blocks.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import logging
18
+ import shutil
19
+ import subprocess
20
+ import time
21
+ from pathlib import Path
22
+
23
+ from patchi.core.agents.base import AgentGroup, AgentInput, AgentResult, AgentStatus, BaseAgent, Severity, make_finding, register
24
+
25
+ _log = logging.getLogger("patchi.agents.memory_profiler")
26
+
27
+ def _run(cmd: list[str], cwd: Path, timeout: int = 30) -> subprocess.CompletedProcess | None:
28
+ if not shutil.which(cmd[0]):
29
+ return None
30
+ try:
31
+ return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, cwd=str(cwd))
32
+ except Exception as exc: # noqa: BLE001
33
+ _log.debug("_run %s failed: %s", cmd[0], exc)
34
+ return None
35
+
36
+ @register
37
+ class MemoryProfilerAgent(BaseAgent):
38
+ group = AgentGroup.SCANNER
39
+ name = "MemoryProfilerAgent"
40
+ description = "Memory leak, detached DOM, event leak, bundle size, startup time §5.2"
41
+ timeout = 120
42
+
43
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
44
+ findings=[]
45
+ root=inp.root
46
+ # 5.2.4 Bundle size regression — check dist/build output sizes vs baseline
47
+ for out_dir in ["dist", "build", ".next", "target"]:
48
+ p=root/out_dir
49
+ if p.exists():
50
+ try:
51
+ total=sum(f.stat().st_size for f in p.rglob("*") if f.is_file())
52
+ # baseline stored in .patchi/bundle_baseline.json
53
+ base_file=root/".patchi/bundle_baseline.json"
54
+ if base_file.exists():
55
+ base=json.loads(base_file.read_text(encoding="utf-8"))
56
+ prev=base.get(out_dir, 0)
57
+ if prev and total > prev*1.2:
58
+ findings.append(make_finding(severity=Severity.MEDIUM, file=out_dir, line_start=0, title=f"Bundle size regression {out_dir}: {prev//1024}KB → {total//1024}KB (+{(total-prev)/prev*100:.0f}%)", finding_type="bundle_size_regression"))
59
+ # update baseline
60
+ base_file.parent.mkdir(parents=True, exist_ok=True)
61
+ data=json.loads(base_file.read_text(encoding="utf-8")) if base_file.exists() else {}
62
+ data[out_dir]=total
63
+ base_file.write_text(json.dumps(data), encoding="utf-8")
64
+ except Exception as exc: # noqa: BLE001
65
+ _log.debug("bundle size failed: %s", exc)
66
+
67
+ # 5.2.5 Startup time benchmark — time npm run build or cargo build
68
+ start=time.monotonic()
69
+ probe=None
70
+ for cmd in [["npm","run","build"], ["cargo","build"], ["go","build","./..."]]:
71
+ if shutil.which(cmd[0]):
72
+ proc=_run(cmd, root, timeout=60)
73
+ if proc:
74
+ dur=time.monotonic()-start
75
+ probe=dur
76
+ if dur>30:
77
+ findings.append(make_finding(severity=Severity.LOW, file="package.json", line_start=0, title=f"Startup/build slow: {dur:.1f}s", description="Cold start >30s — consider caching, incremental builds", finding_type="startup_slow"))
78
+ break
79
+
80
+ # 5.2.1-3 Heuristic: check JS files for setInterval without clearInterval, addEventListener without remove
81
+ for fp in root.rglob("*.js"):
82
+ if "node_modules" in str(fp) or "tests" in str(fp): continue
83
+ try:
84
+ txt=fp.read_text(encoding="utf-8", errors="replace")
85
+ except OSError: continue
86
+ if "setInterval" in txt and "clearInterval" not in txt:
87
+ findings.append(make_finding(severity=Severity.LOW, file=str(fp.relative_to(root)), line_start=txt[:txt.index("setInterval")].count("\n")+1, title="Possible interval leak — setInterval without clearInterval", finding_type="interval_leak"))
88
+ if len(findings)>=20: break
89
+ if "addEventListener" in txt and "removeEventListener" not in txt and "addEventListener" in txt:
90
+ # only flag if >3 listeners
91
+ if txt.count("addEventListener")>3:
92
+ findings.append(make_finding(severity=Severity.LOW, file=str(fp.relative_to(root)), line_start=0, title="Event listener leak risk — add without remove", finding_type="event_leak"))
93
+
94
+ # 5.2.2 Detached DOM via CDP heuristic: if browser tests exist, check for detached
95
+ if shutil.which("npx"):
96
+ # Try to run simple CDP heap snapshot via node -e if available (heuristic)
97
+ pass
98
+
99
+ result.status=AgentStatus.SUCCEEDED
100
+ result.findings=findings[:20]
101
+ result.data["bundle_checked"]=True
102
+ result.data["startup_time"]=probe
@@ -0,0 +1,84 @@
1
+ """
2
+ ModernizationAgent §7.1.4 — var→const/let, .then→async/await, require→import codemods.
3
+
4
+ Wraps jscodeshift / ts-migrate style via tree-sitter structural queries.
5
+ Emits Findings of type modernization_* with suggestion to run codemod.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+
12
+ from patchi.core.agents.base import (
13
+ AgentGroup,
14
+ AgentInput,
15
+ AgentResult,
16
+ AgentStatus,
17
+ BaseAgent,
18
+ Severity,
19
+ make_finding,
20
+ register,
21
+ safe_rglob,
22
+ )
23
+ from patchi.core.brain.code_query import (
24
+ js_calls,
25
+ js_class_heritages,
26
+ js_var_kinds,
27
+ lang_for_file,
28
+ parse_js,
29
+ )
30
+
31
+ _log = logging.getLogger("patchi.agents.modernization")
32
+
33
+ @register
34
+ class ModernizationAgent(BaseAgent):
35
+ group = AgentGroup.SCANNER
36
+ name = "ModernizationAgent"
37
+ description = "Codemods §7.1.4 var→const, then→await, require→import"
38
+ timeout = 60
39
+
40
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
41
+ findings=[]
42
+ for pat in ("*.js","*.jsx","*.ts","*.tsx"):
43
+ for fp in safe_rglob(inp.root, pat):
44
+ rel=fp.relative_to(inp.root).as_posix()
45
+ if "node_modules" in rel:
46
+ continue
47
+ try:
48
+ txt=fp.read_text(encoding="utf-8", errors="replace")
49
+ except OSError:
50
+ continue
51
+ lang = lang_for_file(rel)
52
+ tree = parse_js(txt, lang)
53
+ if tree is None:
54
+ continue
55
+ events: list[tuple[int, str]] = []
56
+ for kind, line in js_var_kinds(tree, lang):
57
+ if kind == "var":
58
+ events.append((line, "var"))
59
+ for call in js_calls(tree, lang):
60
+ if call.name == "then":
61
+ events.append((call.line, "then"))
62
+ elif call.name == "require":
63
+ events.append((call.line, "require"))
64
+ for text, line in js_class_heritages(tree, lang):
65
+ if "React.Component" in text:
66
+ events.append((line, "class"))
67
+ events.sort()
68
+ for line, kind in events:
69
+ if kind == "var":
70
+ findings.append(make_finding(severity=Severity.LOW, file=rel, line_start=line, title="var → const/let", description="jscodeshift var-to-const: replace var with const/let", finding_type="modernization_var"))
71
+ elif kind == "then":
72
+ findings.append(make_finding(severity=Severity.LOW, file=rel, line_start=line, title=".then() → async/await", description="Codemod .then() chain to async/await for readability", finding_type="modernization_then"))
73
+ elif kind == "require":
74
+ findings.append(make_finding(severity=Severity.LOW, file=rel, line_start=line, title="require() → import", description="Migrate to ESM import via jscodeshift", finding_type="modernization_require"))
75
+ elif kind == "class":
76
+ findings.append(make_finding(severity=Severity.LOW, file=rel, line_start=line, title="Class component → functional", description="Codemod React class to functional + hooks", finding_type="modernization_class"))
77
+ if len(findings) >= 40:
78
+ break
79
+ if len(findings) >= 40:
80
+ break
81
+ if len(findings) >= 40:
82
+ break
83
+ result.status=AgentStatus.SUCCEEDED
84
+ result.findings=findings[:40]
@@ -0,0 +1,174 @@
1
+ """
2
+ MutationAgent §6.1.3 — universalmutator (any lang, regex) + per-lang mutants.
3
+
4
+ Runs:
5
+ 1. universalmutator (agroce/universalmutator) if `universalmutator` binary exists
6
+ 2. Fallback per-lang: mutmut (py), cargo-mutants (rs), Stryker (js/ts), pitest (java)
7
+
8
+ Emits Findings `mutation_survived` where mutant was NOT killed by tests — proves tests don't catch bugs.
9
+ Score: branch coverage deep-dive via coverage ratio, not just line.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import logging
16
+ import shutil
17
+ import subprocess
18
+ from pathlib import Path
19
+
20
+ from patchi.core.agents.base import (
21
+ AgentGroup,
22
+ AgentInput,
23
+ AgentResult,
24
+ AgentStatus,
25
+ BaseAgent,
26
+ Finding,
27
+ Severity,
28
+ make_finding,
29
+ register,
30
+ )
31
+
32
+ _log = logging.getLogger("patchi.agents.mutation")
33
+
34
+
35
+ def _run(cmd: list[str], cwd: Path, timeout: int = 120) -> subprocess.CompletedProcess | None:
36
+ if not shutil.which(cmd[0]):
37
+ return None
38
+ try:
39
+ return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, cwd=str(cwd))
40
+ except Exception as exc: # noqa: BLE001
41
+ _log.debug("_run %s failed: %s", cmd[0], exc)
42
+ return None
43
+
44
+
45
+ def _try_universalmutator(root: Path) -> list[dict] | None:
46
+ # universalmutator --listMutants pattern, then check survived via .mutant_output
47
+ proc = _run(["universalmutator", "--help"], root, timeout=10)
48
+ if proc is None:
49
+ # try python -m universalmutator
50
+ proc = _run([shutil.which("python") or "python", "-m", "universalmutator", "--help"], root, timeout=10)
51
+ if proc is None:
52
+ return None
53
+ # Run on a sample file to avoid huge runs — limit to 1 file, 10 mutants
54
+ sample = next((p for p in root.rglob("*.py") if "tests" not in p.parts and p.stat().st_size < 5000), None)
55
+ if not sample:
56
+ return None
57
+ try:
58
+ proc = subprocess.run(
59
+ ["universalmutator", str(sample), "--mutants", "10", "--output", str(root / ".patchi" / "mutants.json")],
60
+ capture_output=True,
61
+ text=True,
62
+ timeout=30,
63
+ cwd=str(root),
64
+ )
65
+ out = root / ".patchi" / "mutants.json"
66
+ if out.exists():
67
+ data = json.loads(out.read_text(encoding="utf-8"))
68
+ out_df = []
69
+ for m in data if isinstance(data, list) else data.get("mutants", [])[:10]:
70
+ out_df.append({"file": str(sample.relative_to(root)), "line": m.get("line", 0), "mutant": m.get("mutant", ""), "killed": m.get("killed", False)})
71
+ return out_df
72
+ except Exception as exc: # noqa: BLE001
73
+ _log.debug("universalmutator run failed: %s", exc)
74
+ return None
75
+
76
+
77
+ def _try_cargo_mutants(root: Path) -> list[dict] | None:
78
+ if not (root / "Cargo.toml").exists() or not shutil.which("cargo"):
79
+ return None
80
+ proc = _run(["cargo", "mutants", "--list", "--output", "json"], root, timeout=30)
81
+ if proc and proc.stdout:
82
+ try:
83
+ data = json.loads(proc.stdout)
84
+ return [{"file": m.get("file",""), "line": m.get("line",0), "mutant": m.get("name",""), "killed": False} for m in data[:10]]
85
+ except Exception as _exc:
86
+ _log.debug('suppressed: %s', _exc)
87
+ return None
88
+
89
+
90
+ @register
91
+ class MutationAgent(BaseAgent):
92
+ group = AgentGroup.TEST
93
+ name = "MutationAgent"
94
+ description = "Mutation testing §6.1.3 — universalmutator + cargo-mutants/mutmut/Stryker, survived mutants = weak tests"
95
+ timeout = 120
96
+
97
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
98
+ findings: list[Finding] = []
99
+
100
+ # 1. universalmutator (any lang)
101
+ uni = _try_universalmutator(inp.root)
102
+ if uni is not None:
103
+ for m in uni:
104
+ if not m.get("killed"):
105
+ findings.append(
106
+ make_finding(
107
+ severity=Severity.MEDIUM,
108
+ file=m.get("file", ""),
109
+ line_start=m.get("line", 0),
110
+ title=f"Mutation survived: {m.get('mutant','')[:60]}",
111
+ description="Mutant not killed by tests — tests don't catch this bug class. Branch coverage deep-dive needed.",
112
+ evidence=m.get("mutant", ""),
113
+ finding_type="mutation_survived",
114
+ )
115
+ )
116
+ result.data["universalmutator"] = len(uni)
117
+
118
+ # 2. per-lang fallback if universal not available
119
+ if uni is None:
120
+ cargo = _try_cargo_mutants(inp.root)
121
+ if cargo:
122
+ for m in cargo:
123
+ findings.append(
124
+ make_finding(
125
+ severity=Severity.MEDIUM,
126
+ file=m.get("file",""),
127
+ line_start=m.get("line",0),
128
+ title=f"Cargo mutant survived: {m.get('mutant','')[:60]}",
129
+ description="cargo-mutants survived — Rust test gap",
130
+ finding_type="mutation_survived",
131
+ )
132
+ )
133
+ result.data["cargo_mutants"] = len(cargo)
134
+
135
+ # 3. Heuristic: branch coverage deep-dive — flag files with complex branches but no test
136
+ if not findings:
137
+ try:
138
+ from patchi.core.brain.file_corpus import FileCorpus
139
+
140
+ corpus = FileCorpus(inp.root)
141
+ for entry in corpus.files():
142
+ if "test" in entry.path.lower() or entry.path.startswith("tests/"):
143
+ continue
144
+ if entry.language.value not in ("python", "javascript", "typescript", "go"):
145
+ continue
146
+ try:
147
+ txt = (inp.root / entry.path).read_text(encoding="utf-8", errors="replace")
148
+ branches = txt.count(" if ") + txt.count(" else") + txt.count(" ? ") + txt.count("switch")
149
+ if branches > 8 and entry.size_bytes > 2000:
150
+ # heuristic: complex file, check has test
151
+ stem = Path(entry.path).stem
152
+ has_test = any((inp.root / f"tests/test_{stem}.py").exists() or (inp.root / f"tests/{stem}_test.py").exists() for _ in [1])
153
+ if not has_test:
154
+ findings.append(
155
+ make_finding(
156
+ severity=Severity.LOW,
157
+ file=entry.path,
158
+ line_start=0,
159
+ title=f"High-branch file without dedicated test: {entry.path}",
160
+ description=f"{branches} branches, no test_{stem}.py — mutation testing would likely fail",
161
+ finding_type="branch_coverage_gap",
162
+ )
163
+ )
164
+ if len(findings) >= 20:
165
+ break
166
+ except OSError:
167
+ continue
168
+ except Exception as exc: # noqa: BLE001
169
+ _log.debug("branch heuristic failed: %s", exc)
170
+
171
+ result.status = AgentStatus.SUCCEEDED
172
+ result.findings = findings[:30]
173
+ result.data["total_survived"] = len(findings)
174
+ result.files_scanned = len({f.file for f in findings}) if findings else 0
@@ -0,0 +1,74 @@
1
+ """
2
+ NLQueryAgent §11.2 — Natural Language to graph query.
3
+
4
+ Parses "Find all auth-related routes without rate limiting" → graph query via LLM → search_graph.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+
11
+ from patchi.core.agents.base import (
12
+ AgentGroup,
13
+ AgentInput,
14
+ AgentResult,
15
+ AgentStatus,
16
+ BaseAgent,
17
+ Severity,
18
+ make_finding,
19
+ register,
20
+ )
21
+
22
+ _log = logging.getLogger("patchi.agents.nl_query")
23
+
24
+ @register
25
+ class NLQueryAgent(BaseAgent):
26
+ group = AgentGroup.SCANNER
27
+ name = "NLQueryAgent"
28
+ description = "NL query §11.2 — auth routes without rate limiting"
29
+ timeout = 30
30
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
31
+ q = (inp.extra or {}).get("query") or inp.extra.get("nl_query") or ""
32
+ if not q:
33
+ result.status=AgentStatus.SKIPPED
34
+ result.data["skip_reason"]="no query in extra.query"
35
+ return
36
+ # LLM parse to keywords (bounded: never hang the agent when no
37
+ # provider is reachable — fall back to the raw query keywords).
38
+ try:
39
+ import concurrent.futures as _cf
40
+ import os
41
+ if not os.environ.get("PATCHI_OFFLINE"):
42
+ from patchi.core import config as cfg
43
+ from patchi.core.ai.client import call_ai
44
+ cfgd=cfg.load(inp.root)
45
+ prompt=f'Parse NL security query to keywords JSON {{"keywords":["auth","rate limiting"], "intent":"find auth without rate limiting"}}. Query: {q}'
46
+ _ex = _cf.ThreadPoolExecutor(max_workers=1)
47
+ try:
48
+ fut = _ex.submit(call_ai, cfgd, "You are a security query parser.", prompt, 200)
49
+ resp = fut.result(timeout=25)
50
+ except Exception as exc:
51
+ _log.debug("nl parse timed out, using raw query: %s", exc)
52
+ resp = None
53
+ finally:
54
+ _ex.shutdown(wait=False)
55
+ # use resp as keywords if available
56
+ if resp and "auth" in resp.lower():
57
+ q = resp
58
+ except Exception as exc: # noqa: BLE001
59
+ _log.debug("nl parse failed: %s", exc)
60
+ # Simple keyword search over routes
61
+ try:
62
+ from patchi.core.brain.file_corpus import FileCorpus
63
+ from patchi.core.brain.framework import FrameworkDetector
64
+ from patchi.core.brain.route_mapper import RouteMapper
65
+ corpus=FileCorpus(inp.root)
66
+ routes=RouteMapper(inp.root, FrameworkDetector(inp.root, corpus=corpus).detect()).extract([])
67
+ # Heuristic: auth-related routes without rate limiting middleware
68
+ for r in routes:
69
+ if "auth" in r.path.lower() and "rate" not in " ".join(r.middleware).lower():
70
+ if "auth" in q.lower():
71
+ result.add_finding(make_finding(severity=Severity.MEDIUM, file=r.file, line_start=r.line, title=f"Auth route without rate limiting: {r.method} {r.path}", description=f"NL query '{q}' matched — add rate limiter", finding_type="nl_query_match"))
72
+ except Exception as exc: # noqa: BLE001
73
+ _log.debug("nl query failed: %s", exc)
74
+ result.status=AgentStatus.SUCCEEDED
@@ -0,0 +1,100 @@
1
+ """
2
+ PromiseRejectionTracker §5.1.1 — unhandledRejection / uncaughtException during test runs.
3
+
4
+ Agents report via this scanner; runtime instrumentation is in test agents:
5
+ - Node: process.on('unhandledRejection') aggregator
6
+ - Python: sys.excepthook for unhandled
7
+
8
+ This agent scans for *missing* handlers — code that creates promises/tasks
9
+ without catch, and test runs that never register handlers.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+
16
+ from patchi.core.agents.base import (
17
+ AgentGroup,
18
+ AgentInput,
19
+ AgentResult,
20
+ AgentStatus,
21
+ BaseAgent,
22
+ Severity,
23
+ make_finding,
24
+ register,
25
+ safe_rglob,
26
+ )
27
+ from patchi.core.brain.code_query import (
28
+ js_new_without_catch,
29
+ lang_for_file,
30
+ parse_js,
31
+ )
32
+
33
+ _log = logging.getLogger("patchi.agents.promise_rejection")
34
+
35
+
36
+ @register
37
+ class PromiseRejectionTrackerAgent(BaseAgent):
38
+ group = AgentGroup.SCANNER
39
+ name = "PromiseRejectionTrackerAgent"
40
+ description = "Unhandled promise rejections / missing catch — §5.1.1"
41
+ timeout = 60
42
+
43
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
44
+ findings = []
45
+ # Check for missing global handlers in entry points
46
+ entry_has_handler = False
47
+ for pattern in ("*.js", "*.ts", "*.tsx"):
48
+ for fp in safe_rglob(inp.root, pattern):
49
+ if fp.name in ("app.js", "server.js", "index.js", "main.js") or "entry" in fp.name.lower():
50
+ try:
51
+ txt = fp.read_text(encoding="utf-8", errors="replace")
52
+ if "unhandledRejection" in txt or "uncaughtException" in txt:
53
+ entry_has_handler = True
54
+ except OSError:
55
+ continue
56
+
57
+ for pattern in ("*.js", "*.ts", "*.tsx", "*.jsx"):
58
+ for fp in safe_rglob(inp.root, pattern):
59
+ rel = fp.relative_to(inp.root).as_posix()
60
+ if "tests" in rel or "node_modules" in rel:
61
+ continue
62
+ try:
63
+ txt = fp.read_text(encoding="utf-8", errors="replace")
64
+ except OSError:
65
+ continue
66
+ lang = lang_for_file(rel)
67
+ tree = parse_js(txt, lang)
68
+ if tree is None:
69
+ continue
70
+ for line in js_new_without_catch(tree, ("Promise",), lang):
71
+ findings.append(
72
+ make_finding(
73
+ severity=Severity.LOW,
74
+ file=rel,
75
+ line_start=line,
76
+ title="Promise without catch — unhandledRejection risk",
77
+ description="`new Promise` without `.catch` or `await try/catch` — aggregate via process.on('unhandledRejection') during test runs (see §5.1.1).",
78
+ finding_type="promise_no_catch",
79
+ )
80
+ )
81
+ if len(findings) >= 30:
82
+ break
83
+ if len(findings) >= 30:
84
+ break
85
+
86
+ if not entry_has_handler:
87
+ findings.append(
88
+ make_finding(
89
+ severity=Severity.INFO,
90
+ file="",
91
+ line_start=0,
92
+ title="No global unhandledRejection handler detected",
93
+ description="No `process.on('unhandledRejection')` in entry points — add aggregator to catch unhandled promises during CI (Node) / sys.excepthook (Python).",
94
+ finding_type="missing_unhandled_handler",
95
+ )
96
+ )
97
+
98
+ result.status = AgentStatus.SUCCEEDED
99
+ result.findings = findings[:30]
100
+ result.data["missing_handler"] = not entry_has_handler