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,108 @@
1
+ """AttackPlanner — orchestrates all attackers, scores and prioritizes hypotheses."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from dataclasses import dataclass
7
+
8
+ from patchi.core.assurance.graph import AssuranceGraph
9
+
10
+ from .api_abuse import ApiAbuseAttacker
11
+ from .auth_bypass import AuthBypassAttacker
12
+ from .base import AttackResult, BaseAttacker, Hypothesis
13
+ from .business_logic import BusinessLogicAttacker
14
+ from .priv_escalation import PrivEscAttacker
15
+ from .recon import ReconAttacker
16
+
17
+ _log = logging.getLogger("patchi.core.attackers.planner")
18
+
19
+ # Objective priority weights (higher = more important to test)
20
+ _OBJECTIVE_PRIORITY: dict[str, float] = {
21
+ "authentication_bypass": 1.0,
22
+ "privilege_escalation": 0.9,
23
+ "api_abuse": 0.7,
24
+ "reconnaissance": 0.5,
25
+ "business_logic_abuse": 0.6,
26
+ }
27
+
28
+ _RISK_SCORES: dict[str, float] = {
29
+ "critical": 1.0,
30
+ "high": 0.7,
31
+ "medium": 0.4,
32
+ "low": 0.2,
33
+ }
34
+
35
+
36
+ @dataclass
37
+ class AttackPlan:
38
+ """A prioritized plan of hypotheses to test."""
39
+
40
+ hypotheses: list[Hypothesis]
41
+ total_score: float = 0.0
42
+
43
+ def to_dict(self) -> dict:
44
+ return {
45
+ "hypotheses": [h.to_dict() for h in self.hypotheses[:20]],
46
+ "total_score": round(self.total_score, 2),
47
+ "count": len(self.hypotheses),
48
+ }
49
+
50
+
51
+ class AttackPlanner:
52
+ """Orchestrate all attackers, score hypotheses, and run the top ones."""
53
+
54
+ def __init__(self, graph: AssuranceGraph, max_run: int = 50):
55
+ self._graph = graph
56
+ self._max_run = max_run
57
+ self._attackers: list[BaseAttacker] = [
58
+ ReconAttacker(graph),
59
+ AuthBypassAttacker(graph),
60
+ PrivEscAttacker(graph),
61
+ BusinessLogicAttacker(graph),
62
+ ApiAbuseAttacker(graph),
63
+ ]
64
+
65
+ def plan(self) -> AttackPlan:
66
+ """Generate and score all hypotheses."""
67
+ all_hypotheses: list[Hypothesis] = []
68
+ for attacker in self._attackers:
69
+ all_hypotheses.extend(attacker.hypothesize())
70
+
71
+ # Score and sort
72
+ for h in all_hypotheses:
73
+ h._score = self._score(h) # type: ignore[attr-defined]
74
+
75
+ all_hypotheses.sort(key=lambda h: getattr(h, "_score", 0), reverse=True)
76
+ total = sum(getattr(h, "_score", 0) for h in all_hypotheses)
77
+
78
+ return AttackPlan(
79
+ hypotheses=all_hypotheses[: self._max_run],
80
+ total_score=total,
81
+ )
82
+
83
+ def run_all(self) -> list[AttackResult]:
84
+ """Run all attackers and collect results."""
85
+ results: list[AttackResult] = []
86
+ for attacker in self._attackers:
87
+ try:
88
+ results.extend(attacker.run())
89
+ except Exception as e:
90
+ _log.warning("Attacker %s failed: %s", attacker.name, e)
91
+ return results
92
+
93
+ def run_campaign(self, objective: str) -> list[AttackResult]:
94
+ """Run attackers matching a specific objective."""
95
+ results: list[AttackResult] = []
96
+ for attacker in self._attackers:
97
+ if attacker.objective == objective:
98
+ try:
99
+ results.extend(attacker.run())
100
+ except Exception as e:
101
+ _log.warning("Attacker %s failed: %s", attacker.name, e)
102
+ return results
103
+
104
+ def _score(self, hypothesis: Hypothesis) -> float:
105
+ """Score a hypothesis by risk × confidence × objective priority."""
106
+ risk = _RISK_SCORES.get(hypothesis.risk, 0.3)
107
+ priority = _OBJECTIVE_PRIORITY.get(hypothesis.objective, 0.3)
108
+ return risk * hypothesis.confidence * priority
@@ -0,0 +1,88 @@
1
+ """PrivEscAttacker — test privilege escalation vulnerabilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .base import AttackResult, BaseAttacker, Hypothesis
6
+
7
+
8
+ class PrivEscAttacker(BaseAttacker):
9
+ """Tests for illegal state transitions, missing isolation, shared endpoints."""
10
+
11
+ name = "priv_escalation"
12
+ objective = "privilege_escalation"
13
+
14
+ def hypothesize(self) -> list[Hypothesis]:
15
+ hypotheses: list[Hypothesis] = []
16
+
17
+ for claim in self._graph.claims.values():
18
+ # Authorization boundary claims
19
+ if "admin" in claim.statement.lower() or "escalat" in claim.statement.lower():
20
+ if claim.verdict.value in ("not_proved", "unproven"):
21
+ hypotheses.append(
22
+ Hypothesis(
23
+ attacker=self.name,
24
+ objective=self.objective,
25
+ target=claim.id,
26
+ risk="critical",
27
+ confidence=0.6,
28
+ description=f"Privilege boundary unverified: {claim.statement}",
29
+ )
30
+ )
31
+
32
+ # Missing tenant isolation
33
+ if "tenant" in claim.statement.lower() or "isolation" in claim.statement.lower():
34
+ hypotheses.append(
35
+ Hypothesis(
36
+ attacker=self.name,
37
+ objective=self.objective,
38
+ target=claim.id,
39
+ risk="critical",
40
+ confidence=0.7,
41
+ description=f"Tenant isolation unverified: {claim.statement}",
42
+ )
43
+ )
44
+
45
+ # Shared endpoints with different privilege levels
46
+ if "role" in claim.statement.lower() and "check" in claim.statement.lower():
47
+ if claim.verdict.value == "unproven":
48
+ hypotheses.append(
49
+ Hypothesis(
50
+ attacker=self.name,
51
+ objective=self.objective,
52
+ target=claim.id,
53
+ risk="high",
54
+ confidence=0.5,
55
+ description=f"Role-based access unverified: {claim.statement}",
56
+ )
57
+ )
58
+
59
+ return hypotheses
60
+
61
+ def test(self, hypothesis: Hypothesis) -> AttackResult:
62
+ claim = self._graph.claims.get(hypothesis.target)
63
+ if claim is None:
64
+ return AttackResult(hypothesis=hypothesis, confirmed=False, evidence="Claim not found")
65
+
66
+ refuting = [e for e in claim.evidence if not e.supports]
67
+ if refuting:
68
+ return AttackResult(
69
+ hypothesis=hypothesis,
70
+ confirmed=False,
71
+ evidence=f"Isolation verified: {refuting[0].detail}",
72
+ )
73
+
74
+ supporting = [e for e in claim.evidence if e.supports]
75
+ if supporting:
76
+ return AttackResult(
77
+ hypothesis=hypothesis,
78
+ confirmed=True,
79
+ evidence=f"Privilege escalation confirmed: {supporting[0].detail}",
80
+ severity="critical",
81
+ remediation="Add role/tenant check before this operation",
82
+ )
83
+
84
+ return AttackResult(
85
+ hypothesis=hypothesis,
86
+ confirmed=False,
87
+ evidence="Insufficient evidence",
88
+ )
@@ -0,0 +1,87 @@
1
+ """ReconAttacker — test reconnaissance vulnerabilities.
2
+
3
+ Looks for unprotected endpoints, excess permissions, info disclosure,
4
+ and unsanitized data flows.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from .base import AttackResult, BaseAttacker, Hypothesis
10
+
11
+
12
+ class ReconAttacker(BaseAttacker):
13
+ """Reconnaissance attacker — finds information disclosure and unprotected endpoints."""
14
+
15
+ name = "recon"
16
+ objective = "reconnaissance"
17
+
18
+ def hypothesize(self) -> list[Hypothesis]:
19
+ hypotheses: list[Hypothesis] = []
20
+
21
+ for claim in self._graph.claims.values():
22
+ # Unprotected endpoints
23
+ if "endpoint" in claim.domain.lower() and "auth" in claim.statement.lower():
24
+ if claim.verdict.value in ("not_proved", "unproven"):
25
+ hypotheses.append(
26
+ Hypothesis(
27
+ attacker=self.name,
28
+ objective=self.objective,
29
+ target=claim.id,
30
+ risk="medium",
31
+ confidence=0.6,
32
+ description=f"Endpoint may be unprotected: {claim.statement}",
33
+ evidence_required=["route scan", "auth check"],
34
+ )
35
+ )
36
+
37
+ # Excess permissions
38
+ if "permission" in claim.statement.lower() or "access" in claim.statement.lower():
39
+ if claim.verdict.value == "unproven":
40
+ hypotheses.append(
41
+ Hypothesis(
42
+ attacker=self.name,
43
+ objective=self.objective,
44
+ target=claim.id,
45
+ risk="medium",
46
+ confidence=0.5,
47
+ description=f"Permission boundary unverified: {claim.statement}",
48
+ evidence_required=["access control check"],
49
+ )
50
+ )
51
+
52
+ return hypotheses
53
+
54
+ def test(self, hypothesis: Hypothesis) -> AttackResult:
55
+ claim = self._graph.claims.get(hypothesis.target)
56
+ if claim is None:
57
+ return AttackResult(
58
+ hypothesis=hypothesis,
59
+ confirmed=False,
60
+ evidence="Claim not found in assurance graph",
61
+ )
62
+
63
+ # Check if any refuting evidence exists (someone already found it's protected)
64
+ refuting = [e for e in claim.evidence if not e.supports]
65
+ if refuting:
66
+ return AttackResult(
67
+ hypothesis=hypothesis,
68
+ confirmed=False,
69
+ evidence=f"Already disproved: {refuting[0].detail}",
70
+ )
71
+
72
+ # Check if any supporting evidence exists (someone confirmed it's unprotected)
73
+ supporting = [e for e in claim.evidence if e.supports]
74
+ if supporting:
75
+ return AttackResult(
76
+ hypothesis=hypothesis,
77
+ confirmed=True,
78
+ evidence=f"Confirmed: {supporting[0].detail}",
79
+ severity=hypothesis.risk,
80
+ remediation="Add authentication/authorization check",
81
+ )
82
+
83
+ return AttackResult(
84
+ hypothesis=hypothesis,
85
+ confirmed=False,
86
+ evidence="Insufficient evidence to confirm or deny",
87
+ )
File without changes
@@ -0,0 +1,81 @@
1
+ """
2
+ AST utilities — language-generic tree-sitter / Python `ast` walking utilities.
3
+
4
+ This package exposes language-generic helpers used by security agents, type
5
+ checkers, and route detectors. The implementation is split into submodules
6
+ but re-exported here so existing imports keep working:
7
+
8
+ from patchi.core.brain.ast_utils import find_calls, find_imports, node_text
9
+ calls = find_calls(content, Lang.PYTHON, ["execute", "eval"])
10
+
11
+ Submodules:
12
+ config.py — per-language node type configs + common sink sets
13
+ helpers.py — node_text, child_by_field, children, get_call_arg, ...
14
+ calls.py — find_calls (+ Python / tree-sitter walkers)
15
+ imports.py — find_imports
16
+ decorators.py — find_decorators
17
+ assignments.py — find_assignments
18
+ control_flow.py — scope / argument walking helpers
19
+ taint.py — source-to-sink taint tracking
20
+ dead_symbols.py — intra-file dead-symbol detection
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from .assignments import find_assignments
26
+ from .calls import find_calls
27
+
28
+ # Re-export public API from submodules
29
+ from .config import (
30
+ CALL_NODE_TYPES,
31
+ DECORATOR_NODE_TYPES,
32
+ EXEC_SINKS,
33
+ HTTP_CLIENTS,
34
+ IMPORT_NODE_TYPES,
35
+ SQL_SINKS,
36
+ )
37
+ from .control_flow import (
38
+ child_by_field_any,
39
+ collect_call_arguments,
40
+ named_children_of_type,
41
+ walk_blocks,
42
+ )
43
+ from .dead_symbols import find_dead_symbols
44
+ from .decorators import find_decorators
45
+ from .helpers import (
46
+ child_by_field,
47
+ children,
48
+ get_call_arg,
49
+ named_children,
50
+ node_text,
51
+ parse_source,
52
+ )
53
+ from .imports import find_imports
54
+ from .taint import SANITIZERS, SOURCES, track_taint
55
+
56
+ __all__ = [
57
+ "CALL_NODE_TYPES",
58
+ "DECORATOR_NODE_TYPES",
59
+ "EXEC_SINKS",
60
+ "HTTP_CLIENTS",
61
+ "IMPORT_NODE_TYPES",
62
+ "SQL_SINKS",
63
+ "child_by_field",
64
+ "children",
65
+ "get_call_arg",
66
+ "named_children",
67
+ "node_text",
68
+ "parse_source",
69
+ "find_calls",
70
+ "find_imports",
71
+ "find_decorators",
72
+ "find_assignments",
73
+ "child_by_field_any",
74
+ "collect_call_arguments",
75
+ "named_children_of_type",
76
+ "walk_blocks",
77
+ "SOURCES",
78
+ "SANITIZERS",
79
+ "track_taint",
80
+ "find_dead_symbols",
81
+ ]
@@ -0,0 +1,219 @@
1
+ """
2
+ AST utilities — variable assignment / declaration finding across languages.
3
+
4
+ Used by env-var analysis, secret scanning, and taint tracking. Returns the
5
+ assigned target name plus the source expression so callers can follow how a
6
+ value (e.g. a secret, a user-controlled string) flows into code.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+
13
+ from patchi.core.brain.languages import get_parser
14
+
15
+ from .config import Lang
16
+ from .helpers import child_by_field, children, node_text
17
+
18
+ _log = logging.getLogger("patchi.brain.assignments")
19
+
20
+
21
+ def find_assignments(content: str, lang: Lang) -> list[dict]:
22
+ """
23
+ Find all variable assignments in `content`.
24
+
25
+ Returns list of dicts with keys:
26
+ target — assigned name (str)
27
+ value — RHS expression text (str)
28
+ line — 1-based line number (int)
29
+ full_text— full assignment text (str)
30
+
31
+ Python is served from the shared single-pass AST scan (one parse + walk
32
+ per file per process, cached).
33
+ """
34
+ if lang == Lang.PYTHON:
35
+ return _find_assignments_python(content)
36
+ parser = get_parser(lang)
37
+ if not parser:
38
+ return []
39
+ try:
40
+ tree = parser.parse(content.encode("utf-8"))
41
+ except Exception as e:
42
+ _log.warning("find_assignments failed: %s", e)
43
+ return []
44
+ results: list[dict] = []
45
+ _walk_assignments(tree.root_node, results, lang)
46
+ return results
47
+
48
+
49
+ # ── Python ────────────────────────────────────────────────────────────────────
50
+
51
+
52
+ def _find_assignments_python(content: str) -> list[dict]:
53
+ """Walk the AST for variable assignments."""
54
+ import ast
55
+
56
+ from .helpers import _py_assign_target_name
57
+
58
+ try:
59
+ tree = ast.parse(content)
60
+ except SyntaxError:
61
+ return []
62
+
63
+ src_lines = content.splitlines()
64
+ results: list[dict] = []
65
+
66
+ def _record(target_node: ast.AST, value_node: ast.AST | None, line_no: int) -> None:
67
+ target = _py_assign_target_name(target_node)
68
+ if not target:
69
+ return
70
+ full_text = src_lines[line_no - 1] if 0 < line_no <= len(src_lines) else ""
71
+ try:
72
+ value = ast.unparse(value_node) if value_node is not None else ""
73
+ except Exception:
74
+ value = ""
75
+ results.append(
76
+ {
77
+ "target": target,
78
+ "value": value,
79
+ "line": line_no,
80
+ "full_text": full_text,
81
+ }
82
+ )
83
+
84
+ for node in ast.walk(tree):
85
+ if isinstance(node, ast.Assign):
86
+ for target in node.targets:
87
+ _record(target, node.value, node.lineno)
88
+ elif isinstance(node, ast.AnnAssign):
89
+ # Annotated assignment: config.debug: bool = True (value may be None)
90
+ _record(node.target, node.value, node.lineno)
91
+ return results
92
+
93
+
94
+ # ── Tree-sitter languages ──────────────────────────────────────────────────────
95
+
96
+ # Per-language assignment node types
97
+ _ASSIGN_NODE_TYPES: dict[Lang, set[str]] = {
98
+ Lang.JAVASCRIPT: {"variable_declarator", "assignment_expression"},
99
+ Lang.TYPESCRIPT: {"variable_declarator", "assignment_expression"},
100
+ Lang.JAVA: {"local_variable_declaration", "assignment_expression"},
101
+ Lang.GO: {"short_var_declaration", "var_declaration", "assignment_statement"},
102
+ Lang.RUST: {"let_declaration", "assignment_expression"},
103
+ Lang.C: {"declaration", "assignment_expression"},
104
+ Lang.CPP: {"declaration", "assignment_expression"},
105
+ Lang.SWIFT: {"variable_declaration"},
106
+ Lang.RUBY: {"assignment"},
107
+ Lang.PHP: {"assignment_expression"},
108
+ Lang.C_SHARP: {"local_declaration_statement", "assignment_expression"},
109
+ Lang.KOTLIN: {"property_declaration", "assignment"},
110
+ Lang.DART: {"variable_declaration"},
111
+ }
112
+
113
+
114
+ def _walk_assignments(node: object, results: list[dict], lang: Lang) -> None:
115
+ try:
116
+ ntype = node.type if hasattr(node, "type") else ""
117
+ except Exception as e:
118
+ _log.warning("_walk_assignments failed: %s", e)
119
+ return
120
+
121
+ assign_types = _ASSIGN_NODE_TYPES.get(lang, set())
122
+ if ntype in assign_types:
123
+ target, value = _extract_assignment(node, lang)
124
+ if target:
125
+ try:
126
+ line = node.start_point[0] + 1 if node.start_point else 0
127
+ except Exception as e:
128
+ _log.warning("_walk_assignments failed: %s", e)
129
+ line = 0
130
+ results.append(
131
+ {
132
+ "target": target,
133
+ "value": value,
134
+ "line": line,
135
+ "full_text": node_text(node),
136
+ }
137
+ )
138
+
139
+ for child in children(node):
140
+ _walk_assignments(child, results, lang)
141
+
142
+
143
+ def _extract_assignment(node: object, lang: Lang) -> tuple[str, str]:
144
+ """Return (target_name, value_text) for an assignment node."""
145
+ if lang in (Lang.JAVASCRIPT, Lang.TYPESCRIPT):
146
+ # variable_declarator: name + value ; assignment_expression: left + right
147
+ if ntype := getattr(node, "type", ""):
148
+ if ntype == "variable_declarator":
149
+ name_node = child_by_field(node, "name")
150
+ val_node = child_by_field(node, "value")
151
+ return node_text(name_node), node_text(val_node)
152
+ if ntype == "assignment_expression":
153
+ left = child_by_field(node, "left")
154
+ right = child_by_field(node, "right")
155
+ return node_text(left), node_text(right)
156
+ if lang == Lang.RUST and getattr(node, "type", "") == "let_declaration":
157
+ # let x = ... ; pattern = identifier, value = value
158
+ pat = child_by_field(node, "pattern") or child_by_field(node, "name")
159
+ val = child_by_field(node, "value")
160
+ return node_text(pat), node_text(val)
161
+ if lang in (Lang.JAVA, Lang.C_SHARP) and getattr(node, "type", "") in (
162
+ "local_variable_declaration",
163
+ "local_declaration_statement",
164
+ ):
165
+ decl = children(node)
166
+ # Find the declarator child holding name/value
167
+ for child in decl:
168
+ ct = child.type
169
+ if ct in ("variable_declarator", "declarator"):
170
+ name_node = child_by_field(child, "name")
171
+ val_node = child_by_field(child, "value")
172
+ return node_text(name_node), node_text(val_node)
173
+ return "", ""
174
+ if lang == Lang.GO:
175
+ if getattr(node, "type", "") == "short_var_declaration":
176
+ left = child_by_field(node, "left")
177
+ right = child_by_field(node, "right")
178
+ return node_text(left), node_text(right)
179
+ if getattr(node, "type", "") in ("var_declaration", "assignment_statement"):
180
+ left = child_by_field(node, "name") or child_by_field(node, "left")
181
+ right = child_by_field(node, "value") or child_by_field(node, "right")
182
+ return node_text(left), node_text(right)
183
+ if lang in (Lang.C, Lang.CPP) and getattr(node, "type", "") == "declaration":
184
+ decl = children(node)
185
+ for child in decl:
186
+ if child.type == "init_declarator":
187
+ name_node = child_by_field(child, "declarator")
188
+ val_node = child_by_field(child, "value")
189
+ return node_text(name_node), node_text(val_node)
190
+ return "", ""
191
+ if lang == Lang.SWIFT and getattr(node, "type", "") == "variable_declaration":
192
+ pat = child_by_field(node, "pattern")
193
+ val = child_by_field(node, "value")
194
+ return node_text(pat), node_text(val)
195
+ if lang == Lang.RUBY and getattr(node, "type", "") == "assignment":
196
+ left = child_by_field(node, "left")
197
+ right = child_by_field(node, "right")
198
+ return node_text(left), node_text(right)
199
+ if lang == Lang.PHP and getattr(node, "type", "") == "assignment_expression":
200
+ left = child_by_field(node, "left")
201
+ right = child_by_field(node, "right")
202
+ return node_text(left), node_text(right)
203
+ if lang == Lang.KOTLIN and getattr(node, "type", "") in ("property_declaration", "assignment"):
204
+ if getattr(node, "type", "") == "property_declaration":
205
+ name_node = child_by_field(node, "name")
206
+ val = child_by_field(node, "initializer")
207
+ return node_text(name_node), node_text(val)
208
+ left = child_by_field(node, "left")
209
+ right = child_by_field(node, "right")
210
+ return node_text(left), node_text(right)
211
+ if lang == Lang.DART and getattr(node, "type", "") == "variable_declaration":
212
+ # Dart uses declarator children
213
+ for child in children(node):
214
+ if child.type == "declared_identifier":
215
+ name_node = child_by_field(child, "name")
216
+ val = child_by_field(node, "value")
217
+ return node_text(name_node), node_text(val)
218
+ return "", ""
219
+ return "", ""