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,58 @@
1
+ """
2
+ BugPredictorAgent §11.1 — AI-powered bug prediction from churn+complexity.
3
+
4
+ Features: git churn (log count), complexity (branch count), bug density (past findings), commit sentiment (heuristic).
5
+ Model: lightweight sklearn-like linear regression if sklearn installed, else heuristic weighted sum.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import subprocess
12
+ from pathlib import Path
13
+
14
+ from patchi.core.agents.base import AgentGroup, AgentInput, AgentResult, AgentStatus, BaseAgent, Severity, make_finding, register
15
+
16
+ _log = logging.getLogger("patchi.agents.bug_predictor")
17
+
18
+ def _churn(root: Path, rel: str) -> int:
19
+ try:
20
+ out = subprocess.run(["git","log","--oneline","--",rel], capture_output=True, text=True, timeout=5, cwd=str(root))
21
+ return len(out.stdout.splitlines()) if out.returncode==0 else 0
22
+ except Exception: return 0
23
+
24
+ def _complexity(txt: str) -> int:
25
+ return txt.count(" if ") + txt.count(" else") + txt.count(" for ") + txt.count(" while ")
26
+
27
+ @register
28
+ class BugPredictorAgent(BaseAgent):
29
+ group = AgentGroup.SCANNER
30
+ name = "BugPredictorAgent"
31
+ description = "AI bug prediction churn×complexity §11.1"
32
+ timeout = 60
33
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
34
+ findings=[]
35
+ # simple heuristic model: score = churn*0.4 + complexity*0.3 + past_bug*0.3
36
+ past = {}
37
+ try:
38
+ from patchi.core import memory as mem
39
+ scans = mem.get_scan_results(inp.root) or {}
40
+ for data in scans.values():
41
+ for f in data.get("findings", []):
42
+ fp=f.get("file","")
43
+ if fp: past[fp]=past.get(fp,0)+1
44
+ except Exception as _exc: _log.debug("suppressed: %s", _exc)
45
+ for fp in inp.root.rglob("*.py"):
46
+ if "tests" in str(fp) or ".patchi" in str(fp): continue
47
+ rel=fp.relative_to(inp.root).as_posix()
48
+ try: txt=fp.read_text(encoding="utf-8", errors="replace")
49
+ except OSError: continue
50
+ churn=_churn(inp.root, rel)
51
+ comp=_complexity(txt)
52
+ bugdens=past.get(rel,0)
53
+ score=churn*0.4 + comp*0.1 + bugdens*2
54
+ if score>15:
55
+ findings.append(make_finding(severity=Severity.LOW, file=rel, line_start=0, title=f"Predicted bug-prone ({score:.0f}) — churn {churn} complexity {comp}", description="High churn+complexity predicts next bug — review, add tests. Features: churn, complexity, bug density; sklearn fallback linear.", finding_type="bug_prediction"))
56
+ if len(findings)>=15: break
57
+ result.status=AgentStatus.SUCCEEDED
58
+ result.findings=findings[:15]
@@ -0,0 +1,150 @@
1
+ """BuildToolValidatorAgent — detects misconfigurations in build tools.
2
+
3
+ Covers §8.3.1:
4
+ - Vite config: missing optimizeDeps, outdated plugins, missing build target
5
+ - Webpack: missing mode, production optimizations, devtool in prod
6
+ - Rollup: missing output format, external deps
7
+ - esbuild: missing target, platform, format
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from pathlib import Path
14
+
15
+ from ..brain.languages import DEFAULT_IGNORE_DIRS
16
+ from .base import (
17
+ AgentGroup,
18
+ AgentInput,
19
+ AgentResult,
20
+ AgentStatus,
21
+ BaseAgent,
22
+ Severity,
23
+ make_finding,
24
+ register,
25
+ safe_rglob,
26
+ )
27
+
28
+ _log = logging.getLogger("patchi.agents.build_tool_validator")
29
+
30
+
31
+ def _check_vite_config(content: str, rel: str) -> list[dict]:
32
+ findings: list[dict] = []
33
+ if "defineConfig" not in content:
34
+ findings.append(
35
+ {"file": rel, "issue": "defineConfig not found — possibly outdated config format"}
36
+ )
37
+ if "optimizeDeps" not in content:
38
+ findings.append({"file": rel, "issue": "Missing optimizeDeps — may slow dev server"})
39
+ if "build" in content and "target" in content and "es2015" in content:
40
+ pass # has build target
41
+ elif "build" in content and "target" not in content:
42
+ findings.append({"file": rel, "issue": "No build.target set — defaults may be too broad"})
43
+ return findings
44
+
45
+
46
+ def _check_webpack_config(content: str, rel: str) -> list[dict]:
47
+ findings: list[dict] = []
48
+ if "mode" not in content:
49
+ findings.append(
50
+ {"file": rel, "issue": "Missing mode — defaults to 'production' but explicit is safer"}
51
+ )
52
+ if "'production'" in content and "'development'" not in content and "devtool" in content:
53
+ findings.append(
54
+ {"file": rel, "issue": "devtool configured — may leak source maps in production"}
55
+ )
56
+ if "MiniCssExtractPlugin" not in content and "css" in content:
57
+ findings.append(
58
+ {"file": rel, "issue": "No MiniCssExtractPlugin — CSS will be inlined in JS"}
59
+ )
60
+ return findings
61
+
62
+
63
+ def _check_rollup_config(content: str, rel: str) -> list[dict]:
64
+ findings: list[dict] = []
65
+ if "output" not in content or (
66
+ "format" not in content and "dir" not in content and "file" not in content
67
+ ):
68
+ findings.append({"file": rel, "issue": "Missing output configuration (format/dir/file)"})
69
+ if "external" not in content:
70
+ findings.append(
71
+ {"file": rel, "issue": "No external dependencies listed — bundle may be bloated"}
72
+ )
73
+ return findings
74
+
75
+
76
+ def _check_esbuild_config(content: str, rel: str) -> list[dict]:
77
+ findings: list[dict] = []
78
+ if "target" not in content:
79
+ findings.append(
80
+ {
81
+ "file": rel,
82
+ "issue": "Missing target — esbuild defaults to esnext which may not be safe",
83
+ }
84
+ )
85
+ if "format" not in content:
86
+ findings.append(
87
+ {"file": rel, "issue": "Missing format (esm/cjs/iife) — output format ambiguous"}
88
+ )
89
+ return findings
90
+
91
+
92
+ @register
93
+ class BuildToolValidatorAgent(BaseAgent):
94
+ """Detects misconfigurations in Vite, Webpack, Rollup, esbuild configs."""
95
+
96
+ group = AgentGroup.SCANNER
97
+ name = "BuildToolValidatorAgent"
98
+ description = "Check Vite/Webpack/Rollup/esbuild configs for known misconfigurations"
99
+
100
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
101
+ config_map: list[tuple[str, str, callable]] = [
102
+ ("vite.config.*", "vite", _check_vite_config),
103
+ ("webpack.config.*", "webpack", _check_webpack_config),
104
+ ("rollup.config.*", "rollup", _check_rollup_config),
105
+ ("esbuild.config.*", "esbuild", _check_esbuild_config),
106
+ ("esbuild.js", "esbuild", _check_esbuild_config),
107
+ ]
108
+ files_scanned = 0
109
+ all_findings: list[dict] = []
110
+
111
+ for glob_pat, tool, checker in config_map:
112
+ for fp in safe_rglob(inp.root, glob_pat):
113
+ rel = fp.relative_to(inp.root).as_posix()
114
+ if any(seg in DEFAULT_IGNORE_DIRS for seg in Path(rel).parts):
115
+ continue
116
+ files_scanned += 1
117
+ try:
118
+ content = fp.read_text(encoding="utf-8")
119
+ except Exception as e:
120
+ _log.warning("BuildToolValidatorAgent._run failed: %s", e)
121
+ continue
122
+ result.findings.append(
123
+ make_finding(
124
+ self.name,
125
+ "build_config_found",
126
+ Severity.INFO,
127
+ rel,
128
+ f"Build config found: {rel} ({tool})",
129
+ )
130
+ )
131
+ issues = checker(content, rel)
132
+ all_findings.extend(issues)
133
+
134
+ result.data["configs_found"] = files_scanned
135
+ result.data["issues"] = all_findings
136
+ result.files_scanned = files_scanned
137
+
138
+ for issue in all_findings:
139
+ result.findings.append(
140
+ make_finding(
141
+ self.name,
142
+ "build_config_issue",
143
+ Severity.LOW,
144
+ issue["file"],
145
+ issue["issue"],
146
+ )
147
+ )
148
+
149
+ if result.status == AgentStatus.RUNNING:
150
+ result.status = AgentStatus.DONE
@@ -0,0 +1,192 @@
1
+ """
2
+ Agent result cache — avoids re-running agents when project files haven't changed.
3
+
4
+ Cache key is an SHA-256 hash of:
5
+ - agent name
6
+ - all project source file (rel_path, mtime_ns, size) tuples
7
+ - config hash
8
+
9
+ Stored in .patchi/cache/agent_cache/{agent_name}.json
10
+
11
+ On a full scan with zero file changes, this skips every agent → ~5-10x speedup.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import hashlib
16
+ import json
17
+ import logging
18
+ import time
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ from patchi.core.agents.base import AgentResult
23
+
24
+ _SOURCE_EXTS = frozenset(
25
+ {
26
+ ".py",
27
+ ".js",
28
+ ".ts",
29
+ ".jsx",
30
+ ".tsx",
31
+ ".go",
32
+ ".rs",
33
+ ".java",
34
+ ".rb",
35
+ ".php",
36
+ ".json",
37
+ ".yaml",
38
+ ".yml",
39
+ ".toml",
40
+ ".cfg",
41
+ ".ini",
42
+ ".env",
43
+ ".html",
44
+ ".css",
45
+ ".scss",
46
+ ".vue",
47
+ ".svelte",
48
+ ".md",
49
+ ".xml",
50
+ ".gradle",
51
+ ".kt",
52
+ ".swift",
53
+ ".mjs",
54
+ ".cjs",
55
+ ".mts",
56
+ ".cts",
57
+ }
58
+ )
59
+
60
+
61
+ _log = logging.getLogger("patchi.agents.cache")
62
+
63
+
64
+ def _file_fingerprint(root: Path) -> str:
65
+ """One fingerprint per project: hash of every source file's content.
66
+
67
+ Content-hashed rather than metadata (mtime_ns, size): two writes landing in
68
+ the same FS timestamp tick with the same size would yield an identical
69
+ metadata fingerprint, so the agent cache would serve stale results after a
70
+ same-sized edit instead of invalidating.
71
+ """
72
+ # .patchi is the tool's own state (memory, action log, agent cache, rules)
73
+ # — never part of the project's source. Including it made the fingerprint
74
+ # change every time a cache/memory file was written, so the cache kept
75
+ # invalidating ITSELF mid-pipeline (found by the smoke-sweep --pipeline
76
+ # orchestration gate: the Governor's SANDBOX_REVERIFY re-ran 267 agents
77
+ # instead of hitting cache).
78
+ _IGNORED_DIRS = {
79
+ "node_modules",
80
+ ".git",
81
+ "__pycache__",
82
+ ".venv",
83
+ "venv",
84
+ ".tox",
85
+ ".eggs",
86
+ "eggs",
87
+ "dist",
88
+ "build",
89
+ ".ruff_cache",
90
+ ".pytest_cache",
91
+ ".mypy_cache",
92
+ ".coverage",
93
+ ".patchi",
94
+ }
95
+
96
+ hasher = hashlib.sha256()
97
+ for fpath in sorted(root.rglob("*")):
98
+ parts = fpath.relative_to(root).parts if fpath != root else ()
99
+ if any(p in _IGNORED_DIRS for p in parts):
100
+ continue
101
+ if fpath.is_file() and fpath.suffix in _SOURCE_EXTS:
102
+ try:
103
+ rel = fpath.relative_to(root).as_posix()
104
+ hasher.update(f"f:{rel}:".encode())
105
+ with fpath.open("rb") as fh:
106
+ hasher.update(hashlib.sha256(fh.read()).digest())
107
+ hasher.update(b"\n")
108
+ except (OSError, ValueError):
109
+ continue
110
+ return hasher.hexdigest()[:16]
111
+
112
+
113
+ class AgentCache:
114
+ """Per-agent on-disk result cache, invalidated when project files change."""
115
+
116
+ def __init__(self, root: Path):
117
+ self._root = root
118
+ self._cache_dir = root / ".patchi" / "cache" / "agent_cache"
119
+ self._cache_dir.mkdir(parents=True, exist_ok=True)
120
+ self._fingerprint: str | None = None
121
+
122
+ def get_fingerprint(self) -> str:
123
+ if self._fingerprint is None:
124
+ self._fingerprint = _file_fingerprint(self._root)
125
+ return self._fingerprint
126
+
127
+ def _cache_path(self, agent_name: str) -> Path:
128
+ safe = agent_name.replace("/", "_").replace("\\", "_")
129
+ return self._cache_dir / f"{safe}.json"
130
+
131
+ def get(self, agent_name: str) -> AgentResult | None:
132
+ path = self._cache_path(agent_name)
133
+ if not path.exists():
134
+ return None
135
+ try:
136
+ data = json.loads(path.read_text(encoding="utf-8"))
137
+ if data.get("fp") == self.get_fingerprint():
138
+ result = AgentResult.from_dict(data["result"])
139
+ return result
140
+ except Exception as e:
141
+ _log.warning("AgentCache.get failed: %s", e)
142
+ return None
143
+
144
+ def put(self, agent_name: str, result: AgentResult) -> None:
145
+ path = self._cache_path(agent_name)
146
+ try:
147
+ data: dict[str, Any] = {
148
+ "fp": self.get_fingerprint(),
149
+ "result": result.to_dict(),
150
+ "ts": time.time(),
151
+ }
152
+ path.write_text(json.dumps(data, indent=2), encoding="utf-8")
153
+ except Exception as e:
154
+ _log.warning("AgentCache.put failed: %s", e)
155
+
156
+ def invalidate_all(self) -> int:
157
+ """Clear all cached agent results. Returns count of files removed."""
158
+ count = 0
159
+ if self._cache_dir.exists():
160
+ for fpath in self._cache_dir.glob("*.json"):
161
+ try:
162
+ fpath.unlink()
163
+ count += 1
164
+ except OSError:
165
+ pass
166
+ self._fingerprint = None
167
+ return count
168
+
169
+ def invalidate(self, agent_name: str) -> bool:
170
+ """Clear cache for a single agent."""
171
+ path = self._cache_path(agent_name)
172
+ if path.exists():
173
+ try:
174
+ path.unlink()
175
+ return True
176
+ except OSError:
177
+ pass
178
+ return False
179
+
180
+ def list_cached(self) -> list[str]:
181
+ """Return agent names with valid cache entries."""
182
+ results = []
183
+ fp = self.get_fingerprint()
184
+ if self._cache_dir.exists():
185
+ for fpath in self._cache_dir.glob("*.json"):
186
+ try:
187
+ data = json.loads(fpath.read_text(encoding="utf-8"))
188
+ if data.get("fp") == fp:
189
+ results.append(fpath.stem)
190
+ except Exception as e:
191
+ _log.warning("AgentCache.list_cached failed: %s", e)
192
+ return results
@@ -0,0 +1,133 @@
1
+ """
2
+ ChaosAgent §5.3.3-4 — Race Condition + Chaos Testing.
3
+
4
+ Race: concurrent duplicate requests to same endpoint → duplicate records.
5
+ Chaos: missing retry/backoff on network, no circuit breaker.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ import logging
12
+
13
+ from patchi.core.agents.base import (
14
+ AgentGroup,
15
+ AgentInput,
16
+ AgentResult,
17
+ AgentStatus,
18
+ BaseAgent,
19
+ Severity,
20
+ make_finding,
21
+ register,
22
+ safe_rglob,
23
+ )
24
+ from patchi.core.brain.code_query import (
25
+ js_calls,
26
+ js_identifiers,
27
+ js_string_literals,
28
+ lang_for_file,
29
+ parse_js,
30
+ )
31
+
32
+ _log = logging.getLogger("patchi.agents.chaos")
33
+
34
+ _RACE_WORDS = ("create", "insert", "update", "upsert", "transaction")
35
+ _RETRY_WORDS = ("retry", "backoff", "circuit", "breaker", "tenacity", "resilience")
36
+
37
+
38
+ def _py_tokens(content: str) -> tuple[set[str], set[str], list[tuple[str, int]]]:
39
+ """(call names, word tokens, [(call, line)]) for Python via stdlib ast."""
40
+ calls: list[tuple[str, int]] = []
41
+ words: set[str] = set()
42
+ try:
43
+ tree = ast.parse(content)
44
+ except SyntaxError:
45
+ return set(), set(), []
46
+ for node in ast.walk(tree):
47
+ if isinstance(node, ast.Call):
48
+ func = node.func
49
+ name = func.id if isinstance(func, ast.Name) else getattr(func, "attr", "")
50
+ if name:
51
+ calls.append((name, node.lineno))
52
+ words.add(name.lower())
53
+ elif isinstance(node, ast.Name):
54
+ words.add(node.id.lower())
55
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
56
+ words.add(node.name.lower())
57
+ elif isinstance(node, ast.Constant) and isinstance(node.value, str):
58
+ for word in node.value.lower().split():
59
+ words.add(word.strip(".,:;!?()[]{}\"'"))
60
+ return {c[0] for c in calls}, words, calls
61
+
62
+
63
+ def _has_race_words(words: set[str]) -> bool:
64
+ return any(any(rw in w for w in words) for rw in _RACE_WORDS)
65
+
66
+
67
+ def _has_retry_words(words: set[str]) -> bool:
68
+ return any(any(rw in w for w in words) for rw in _RETRY_WORDS)
69
+
70
+ @register
71
+ class ChaosAgent(BaseAgent):
72
+ group = AgentGroup.TEST
73
+ name = "ChaosAgent"
74
+ description = "Race duplicate requests + Chaos missing retry/backoff §5.3.3-4"
75
+ timeout = 60
76
+
77
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
78
+ findings=[]
79
+ for pat in ("*.py","*.js","*.ts"):
80
+ for fp in safe_rglob(inp.root, pat):
81
+ rel=fp.relative_to(inp.root).as_posix()
82
+ if "tests" in rel or "node_modules" in rel:
83
+ continue
84
+ try:
85
+ txt=fp.read_text(encoding="utf-8", errors="replace")
86
+ except OSError:
87
+ continue
88
+ if fp.suffix == ".py":
89
+ _, words, calls = _py_tokens(txt)
90
+ open_lines = [ln for n, ln in calls if n == "open"]
91
+ net_lines = [
92
+ ln for n, ln in calls if n in ("get", "post", "request", "urlopen")
93
+ ]
94
+ else:
95
+ lang = lang_for_file(rel)
96
+ tree = parse_js(txt, lang)
97
+ if tree is None:
98
+ continue
99
+ words = {i.lower() for i in js_identifiers(tree, lang)}
100
+ words |= {s.lower() for s, _ in js_string_literals(tree, lang)}
101
+ js_call_list = js_calls(tree, lang)
102
+ open_lines = [c.line for c in js_call_list if c.name == "open"]
103
+ net_lines = [
104
+ c.line for c in js_call_list
105
+ if c.full == "fetch" or c.full.startswith("axios.")
106
+ ]
107
+ # Race: file write without lock/atomic
108
+ if open_lines and not words & {"lock", "atomic", "mutex"}:
109
+ if _has_race_words(words):
110
+ findings.append(make_finding(
111
+ severity=Severity.MEDIUM, file=rel, line_start=open_lines[0],
112
+ title="Possible race — file write without lock",
113
+ description=(
114
+ "Concurrent requests may duplicate records; "
115
+ "use atomic write or DB transaction"
116
+ ),
117
+ finding_type="race_file_write",
118
+ ))
119
+ # Chaos: fetch without retry
120
+ if len(net_lines) > 2 and not _has_retry_words(words):
121
+ findings.append(make_finding(
122
+ severity=Severity.LOW, file=rel, line_start=0,
123
+ title="No retry/backoff on network calls",
124
+ description="Add retry with backoff + circuit breaker for chaos resilience",
125
+ finding_type="chaos_no_retry",
126
+ ))
127
+ break
128
+ if len(findings) >= 20:
129
+ break
130
+ if len(findings) >= 20:
131
+ break
132
+ result.status=AgentStatus.SUCCEEDED
133
+ result.findings=findings[:20]