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,88 @@
1
+ """PHP type checker — uses tree-sitter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from ..languages import Lang, get_parser
9
+ from .base import BaseTypeChecker, _node_text, make_finding
10
+
11
+ _log = logging.getLogger("patchi.brain.php")
12
+
13
+
14
+ class PHPTypeChecker(BaseTypeChecker):
15
+ language = "php"
16
+
17
+ def check(self, source: str, file_path: str) -> list[dict]:
18
+ findings: list[dict] = []
19
+ parser = get_parser(Lang.PHP)
20
+ if parser is None:
21
+ return findings
22
+ try:
23
+ tree = parser.parse(source.encode("utf-8"))
24
+ self._walk(tree.root_node, source, file_path, findings)
25
+ except Exception as e:
26
+ _log.warning("PHPTypeChecker.check failed: %s", e)
27
+ return findings
28
+
29
+ def _walk(self, node: Any, source: str, file_path: str, findings: list[dict]) -> None:
30
+ ntype = node.type
31
+ if ntype in ("function_definition", "method_declaration"):
32
+ line = node.start_point[0] + 1
33
+ name = (
34
+ _node_text(source, self._child_by_type(node, "name"))
35
+ if self._child_by_type(node, "name")
36
+ else ""
37
+ )
38
+ return_type = (
39
+ self._child_by_type(node, "primitive_type")
40
+ or self._child_by_type(node, "named_type")
41
+ or self._child_by_type(node, "nullable_type")
42
+ )
43
+ if return_type is None:
44
+ findings.append(
45
+ make_finding(
46
+ finding_type="missing_return_type",
47
+ file=file_path,
48
+ line=line,
49
+ title="Missing return type hint",
50
+ description=f"Function '{name}' is missing a PHP return type hint",
51
+ evidence=f"function {name}()",
52
+ severity="low",
53
+ )
54
+ )
55
+ # Check param type hints
56
+ params = self._child_by_type(node, "formal_parameters")
57
+ if params:
58
+ for child in params.children:
59
+ if child.type == "simple_parameter":
60
+ ptype = (
61
+ self._child_by_type(child, "primitive_type")
62
+ or self._child_by_type(child, "named_type")
63
+ or self._child_by_type(child, "nullable_type")
64
+ )
65
+ if ptype is None:
66
+ param_name = (
67
+ _node_text(source, self._child_by_type(child, "variable_name"))
68
+ if self._child_by_type(child, "variable_name")
69
+ else "?"
70
+ )
71
+ findings.append(
72
+ make_finding(
73
+ finding_type="missing_param_type",
74
+ file=file_path,
75
+ line=line,
76
+ title="Missing parameter type hint",
77
+ description=f"Parameter '{param_name}' in '{name}' missing type hint",
78
+ severity="low",
79
+ )
80
+ )
81
+ for child in node.children:
82
+ self._walk(child, source, file_path, findings)
83
+
84
+ def _child_by_type(self, node: Any, ntype: str) -> Any | None:
85
+ for child in node.children:
86
+ if child.type == ntype:
87
+ return child
88
+ return None
@@ -0,0 +1,87 @@
1
+ """Python type checker — uses stdlib ast."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+
7
+ from .base import BaseTypeChecker, make_finding
8
+
9
+
10
+ class PythonTypeChecker(BaseTypeChecker):
11
+ language = "python"
12
+
13
+ def check(self, source: str, file_path: str) -> list[dict]:
14
+ findings: list[dict] = []
15
+ try:
16
+ tree = ast.parse(source)
17
+ except SyntaxError:
18
+ return findings
19
+
20
+ lines = source.split("\n")
21
+ for node in ast.walk(tree):
22
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
23
+ self._check_function_def(node, lines, file_path, findings)
24
+ elif isinstance(node, ast.AnnAssign):
25
+ self._check_annotation(node, lines, file_path, findings)
26
+
27
+ return findings
28
+
29
+ def _check_function_def(
30
+ self,
31
+ node: ast.FunctionDef | ast.AsyncFunctionDef,
32
+ lines: list[str],
33
+ file_path: str,
34
+ findings: list[dict],
35
+ ) -> None:
36
+ line = node.lineno
37
+
38
+ # Missing return type annotation
39
+ if node.returns is None:
40
+ if node.name not in ("__init__", "__new__", "__post_init__"):
41
+ findings.append(
42
+ make_finding(
43
+ finding_type="missing_return_type",
44
+ file=file_path,
45
+ line=line,
46
+ title="Missing return type annotation",
47
+ description=f"Function '{node.name}' is missing a return type annotation",
48
+ evidence=f"def {node.name}",
49
+ severity="low",
50
+ )
51
+ )
52
+
53
+ # Parameters without type annotations
54
+ for arg in node.args.args:
55
+ if arg.arg == "self":
56
+ continue
57
+ if arg.annotation is None:
58
+ findings.append(
59
+ make_finding(
60
+ finding_type="missing_param_type",
61
+ file=file_path,
62
+ line=line,
63
+ title="Missing parameter type annotation",
64
+ description=f"Parameter '{arg.arg}' in function '{node.name}' is missing a type annotation",
65
+ evidence=f"{arg.arg}: ?",
66
+ severity="low",
67
+ )
68
+ )
69
+
70
+ def _check_annotation(
71
+ self, node: ast.AnnAssign, lines: list[str], file_path: str, findings: list[dict]
72
+ ) -> None:
73
+ line = node.lineno
74
+ ann = node.annotation
75
+ if isinstance(ann, ast.Name) and ann.id == "Any":
76
+ target = ast.unparse(node.target) if hasattr(ast, "unparse") else ""
77
+ findings.append(
78
+ make_finding(
79
+ finding_type="explicit_any",
80
+ file=file_path,
81
+ line=line,
82
+ title="Variable typed as 'Any'",
83
+ description=f"Variable '{target}' is typed as 'Any' (bypasses type checking)",
84
+ evidence=f"{target}: Any",
85
+ severity="medium",
86
+ )
87
+ )
@@ -0,0 +1,81 @@
1
+ """Rust type checker — uses tree-sitter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from ..languages import Lang, get_parser
9
+ from .base import BaseTypeChecker, _node_text, make_finding
10
+
11
+ _log = logging.getLogger("patchi.brain.rust")
12
+
13
+
14
+ class RustTypeChecker(BaseTypeChecker):
15
+ language = "rust"
16
+
17
+ def check(self, source: str, file_path: str) -> list[dict]:
18
+ findings: list[dict] = []
19
+ parser = get_parser(Lang.RUST)
20
+ if parser is None:
21
+ return findings
22
+ try:
23
+ tree = parser.parse(source.encode("utf-8"))
24
+ self._walk(tree.root_node, source, file_path, findings)
25
+ except Exception as e:
26
+ _log.warning("RustTypeChecker.check failed: %s", e)
27
+ return findings
28
+
29
+ def _walk(self, node: Any, source: str, file_path: str, findings: list[dict]) -> None:
30
+ ntype = node.type
31
+ if ntype == "function_item":
32
+ line = node.start_point[0] + 1
33
+ name = (
34
+ _node_text(source, self._child_by_type(node, "identifier"))
35
+ if self._child_by_type(node, "identifier")
36
+ else ""
37
+ )
38
+ return_type = self._child_by_type(node, "return_type")
39
+ if return_type:
40
+ for child in return_type.children:
41
+ if child.type == "type_identifier" and _node_text(source, child) == "Box":
42
+ inner = self._child_by_type(child, "type_arguments")
43
+ if inner and _node_text(source, inner).find("dyn") >= 0:
44
+ findings.append(
45
+ make_finding(
46
+ finding_type="trait_object",
47
+ file=file_path,
48
+ line=line,
49
+ title="Returns Box<dyn Trait> — consider generics",
50
+ description=f"Function '{name}' returns Box<dyn ...> — consider `impl Trait` or generics",
51
+ evidence=f"{name}: Box<dyn ...>",
52
+ severity="low",
53
+ )
54
+ )
55
+ # Check for unwrap() calls
56
+ for child in node.children:
57
+ self._walk(child, source, file_path, findings)
58
+ elif ntype == "call_expression":
59
+ line = node.start_point[0] + 1
60
+ fn_name = _node_text(source, node).split("(")[0].strip()
61
+ if fn_name == "unwrap" or fn_name.endswith(".unwrap"):
62
+ findings.append(
63
+ make_finding(
64
+ finding_type="unwrap_call",
65
+ file=file_path,
66
+ line=line,
67
+ title="Unwrap call — may panic",
68
+ description="Call to .unwrap() may panic on None/Err — handle with match or ?",
69
+ evidence=fn_name,
70
+ severity="medium",
71
+ )
72
+ )
73
+ else:
74
+ for child in node.children:
75
+ self._walk(child, source, file_path, findings)
76
+
77
+ def _child_by_type(self, node: Any, ntype: str) -> Any | None:
78
+ for child in node.children:
79
+ if child.type == ntype:
80
+ return child
81
+ return None
@@ -0,0 +1,59 @@
1
+ """Swift type checker — uses tree-sitter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from ..languages import Lang, get_parser
9
+ from .base import BaseTypeChecker, _node_text, make_finding
10
+
11
+ _log = logging.getLogger("patchi.brain.swift")
12
+
13
+
14
+ class SwiftTypeChecker(BaseTypeChecker):
15
+ language = "swift"
16
+
17
+ def check(self, source: str, file_path: str) -> list[dict]:
18
+ findings: list[dict] = []
19
+ parser = get_parser(Lang.SWIFT)
20
+ if parser is None:
21
+ return findings
22
+ try:
23
+ tree = parser.parse(source.encode("utf-8"))
24
+ self._walk(tree.root_node, source, file_path, findings)
25
+ except Exception as e:
26
+ _log.warning("SwiftTypeChecker.check failed: %s", e)
27
+ return findings
28
+
29
+ def _walk(self, node: Any, source: str, file_path: str, findings: list[dict]) -> None:
30
+ ntype = node.type
31
+ if ntype == "function_declaration":
32
+ line = node.start_point[0] + 1
33
+ name = (
34
+ _node_text(source, self._child_by_type(node, "identifier"))
35
+ if self._child_by_type(node, "identifier")
36
+ else ""
37
+ )
38
+ return_type = self._child_by_type(node, "return_type")
39
+ if return_type:
40
+ rt_text = _node_text(source, return_type).replace("-> ", "")
41
+ if rt_text == "Any":
42
+ findings.append(
43
+ make_finding(
44
+ finding_type="any_type",
45
+ file=file_path,
46
+ line=line,
47
+ title="Function returns 'Any'",
48
+ description=f"Function '{name}' returns 'Any' — consider a specific type",
49
+ severity="medium",
50
+ )
51
+ )
52
+ for child in node.children:
53
+ self._walk(child, source, file_path, findings)
54
+
55
+ def _child_by_type(self, node: Any, ntype: str) -> Any | None:
56
+ for child in node.children:
57
+ if child.type == ntype:
58
+ return child
59
+ return None
@@ -0,0 +1,298 @@
1
+ """TypeScript type checker — uses tree-sitter AST instead of regex."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Any
7
+
8
+ from ..languages import Lang, get_parser
9
+ from .base import BaseTypeChecker, _node_text, make_finding
10
+
11
+ _log = logging.getLogger("patchi.brain.typescript")
12
+
13
+
14
+ class TypeScriptChecker(BaseTypeChecker):
15
+ language = "typescript"
16
+
17
+ def check(self, source: str, file_path: str) -> list[dict]:
18
+ findings: list[dict] = []
19
+ parser = get_parser(Lang.TYPESCRIPT)
20
+ if parser is None:
21
+ return findings
22
+
23
+ try:
24
+ tree = parser.parse(source.encode("utf-8"))
25
+ buf = source.encode("utf-8")
26
+ self._walk(tree.root_node, buf, source, file_path, findings)
27
+ except Exception as e:
28
+ _log.warning("TypeScriptChecker.check failed: %s", e)
29
+
30
+ return findings
31
+
32
+ def _walk(
33
+ self, node: Any, buf: bytes, source: str, file_path: str, findings: list[dict]
34
+ ) -> None:
35
+ ntype = node.type
36
+
37
+ if ntype in ("function_declaration", "method_definition"):
38
+ self._check_function(node, buf, source, file_path, findings)
39
+ return
40
+ elif ntype == "arrow_function":
41
+ self._check_arrow_function(node, buf, source, file_path, findings)
42
+ return
43
+ elif ntype == "variable_declarator":
44
+ self._check_variable(node, buf, source, file_path, findings)
45
+ elif ntype == "required_parameter":
46
+ self._check_parameter(node, buf, source, file_path, findings)
47
+ elif ntype == "export_statement":
48
+ for child in node.named_children if hasattr(node, "named_children") else node.children:
49
+ self._walk(child, buf, source, file_path, findings)
50
+ return
51
+ elif ntype == "as_expression":
52
+ self._check_as_cast(node, buf, source, file_path, findings)
53
+ elif ntype == "comment":
54
+ self._check_comment(node, buf, source, file_path, findings)
55
+ return
56
+
57
+ for child in node.named_children if hasattr(node, "named_children") else node.children:
58
+ self._walk(child, buf, source, file_path, findings)
59
+
60
+ def _check_function(
61
+ self, node: Any, buf: bytes, source: str, file_path: str, findings: list[dict]
62
+ ) -> None:
63
+ line = node.start_point[0] + 1
64
+
65
+ # Check return type annotation
66
+ return_type = self._child_by_field(node, "return_type")
67
+ name = self._child_text_by_field(node, "name", buf) or "anonymous"
68
+
69
+ if return_type is None and name != "constructor":
70
+ body_text = _node_text(source, node)
71
+ if "=>" not in body_text:
72
+ findings.append(
73
+ make_finding(
74
+ finding_type="missing_return_type",
75
+ file=file_path,
76
+ line=line,
77
+ title="Missing return type on function",
78
+ description=f"Function '{name}' is missing an explicit return type annotation",
79
+ evidence=f"function {name}",
80
+ severity="low",
81
+ )
82
+ )
83
+ elif return_type is not None:
84
+ # return_type is a type_annotation node — look inside for "any"
85
+ inner = self._find_predefined_type(return_type)
86
+ if inner == "any":
87
+ findings.append(
88
+ make_finding(
89
+ finding_type="explicit_any",
90
+ file=file_path,
91
+ line=line,
92
+ title="Function returns 'any'",
93
+ description=f"Function '{name}' returns 'any' type",
94
+ evidence=f"function {name}: any",
95
+ severity="medium",
96
+ )
97
+ )
98
+
99
+ # Check parameters for 'any'
100
+ params = self._child_by_field(node, "parameters")
101
+ if params:
102
+ self._check_parameter_list(params, buf, source, file_path, findings)
103
+
104
+ def _check_arrow_function(
105
+ self, node: Any, buf: bytes, source: str, file_path: str, findings: list[dict]
106
+ ) -> None:
107
+ line = node.start_point[0] + 1
108
+
109
+ return_type = self._child_by_field(node, "return_type")
110
+ if return_type is None:
111
+ parent = getattr(node, "parent", None)
112
+ if parent and parent.type == "variable_declarator":
113
+ name_node = self._child_by_field(parent, "name")
114
+ name = _node_text(source, name_node) if name_node else "anonymous"
115
+ findings.append(
116
+ make_finding(
117
+ finding_type="missing_return_type",
118
+ file=file_path,
119
+ line=line,
120
+ title="Missing return type on arrow function",
121
+ description=f"Arrow function '{name}' is missing explicit return type",
122
+ evidence=f"const {name} = ... =>",
123
+ severity="low",
124
+ )
125
+ )
126
+ elif return_type is not None:
127
+ inner = self._find_predefined_type(return_type)
128
+ if inner == "any":
129
+ findings.append(
130
+ make_finding(
131
+ finding_type="explicit_any",
132
+ file=file_path,
133
+ line=line,
134
+ title="Arrow function returns 'any'",
135
+ description="Arrow function returns 'any' type",
136
+ severity="medium",
137
+ )
138
+ )
139
+
140
+ params = self._child_by_field(node, "parameters")
141
+ if params:
142
+ self._check_parameter_list(params, buf, source, file_path, findings)
143
+
144
+ def _check_variable(
145
+ self, node: Any, buf: bytes, source: str, file_path: str, findings: list[dict]
146
+ ) -> None:
147
+ line = node.start_point[0] + 1
148
+ name_node = self._child_by_field(node, "name")
149
+ name = _node_text(source, name_node) if name_node else ""
150
+
151
+ # Check if variable has a type annotation
152
+ type_node = self._child_by_field(node, "type")
153
+ if type_node is None:
154
+ value_node = self._child_by_field(node, "value")
155
+ if value_node and value_node.type not in (
156
+ "arrow_function",
157
+ "function",
158
+ "object",
159
+ "array",
160
+ ):
161
+ findings.append(
162
+ make_finding(
163
+ finding_type="missing_type",
164
+ file=file_path,
165
+ line=line,
166
+ title="Missing type annotation on variable",
167
+ description=f"Variable '{name}' is missing explicit type annotation",
168
+ evidence=f"{name} = ...",
169
+ severity="low",
170
+ )
171
+ )
172
+ elif type_node is not None:
173
+ inner = self._find_predefined_type(type_node)
174
+ if inner == "any":
175
+ findings.append(
176
+ make_finding(
177
+ finding_type="explicit_any",
178
+ file=file_path,
179
+ line=line,
180
+ title="Variable typed as 'any'",
181
+ description=f"Variable '{name}' is explicitly typed as 'any'",
182
+ evidence=f"{name}: any",
183
+ severity="medium",
184
+ )
185
+ )
186
+
187
+ def _check_parameter(
188
+ self, node: Any, buf: bytes, source: str, file_path: str, findings: list[dict]
189
+ ) -> None:
190
+ line = node.start_point[0] + 1
191
+ name_node = self._child_by_field(node, "name")
192
+ name = _node_text(source, name_node) if name_node else ""
193
+
194
+ type_node = self._child_by_field(node, "type")
195
+ if type_node is not None:
196
+ inner = self._find_predefined_type(type_node)
197
+ if inner == "any":
198
+ findings.append(
199
+ make_finding(
200
+ finding_type="explicit_any",
201
+ file=file_path,
202
+ line=line,
203
+ title="Parameter typed as 'any'",
204
+ description=f"Parameter '{name}' is typed as 'any'",
205
+ evidence=f"{name}: any",
206
+ severity="medium",
207
+ )
208
+ )
209
+
210
+ def _check_parameter_list(
211
+ self, params_node: Any, buf: bytes, source: str, file_path: str, findings: list[dict]
212
+ ) -> None:
213
+ for child in (
214
+ params_node.named_children
215
+ if hasattr(params_node, "named_children")
216
+ else params_node.children
217
+ ):
218
+ if child.type == "required_parameter":
219
+ self._check_parameter(child, buf, source, file_path, findings)
220
+ elif child.type == "optional_parameter":
221
+ self._check_parameter(child, buf, source, file_path, findings)
222
+
223
+ def _find_predefined_type(self, type_ann: Any) -> str:
224
+ """Look inside a type_annotation node for a predefined_type like 'any'."""
225
+ for child in type_ann.children if hasattr(type_ann, "children") else []:
226
+ if child.type == "predefined_type":
227
+ try:
228
+ raw = child.text
229
+ if isinstance(raw, bytes):
230
+ return raw.decode("utf-8", errors="replace")
231
+ return str(raw)
232
+ except Exception as e:
233
+ _log.warning("TypeScriptChecker._find_predefined_type failed: %s", e)
234
+ return ""
235
+
236
+ def _child_by_field(self, node: Any, field: str) -> Any | None:
237
+ if hasattr(node, "child_by_field_name"):
238
+ return node.child_by_field_name(field)
239
+ return None
240
+
241
+ def _child_text_by_field(self, node: Any, field: str, buf: bytes) -> str:
242
+ child = self._child_by_field(node, field)
243
+ if child:
244
+ try:
245
+ return buf[child.start_byte : child.end_byte].decode("utf-8", errors="replace")
246
+ except Exception as e:
247
+ _log.warning("TypeScriptChecker._child_text_by_field failed: %s", e)
248
+ return ""
249
+
250
+ def _check_as_cast(
251
+ self, node: Any, buf: bytes, source: str, file_path: str, findings: list[dict]
252
+ ) -> None:
253
+ line = node.start_point[0] + 1
254
+ type_node = self._child_by_field(node, "type")
255
+ type_name = "unknown"
256
+ if type_node:
257
+ type_name = _node_text(source, type_node)[:50]
258
+ findings.append(
259
+ make_finding(
260
+ finding_type="unsafe_cast",
261
+ file=file_path,
262
+ line=line,
263
+ title="Unsafe type cast",
264
+ description=f"Using 'as' type assertion to '{type_name}'",
265
+ evidence=f"as {type_name}",
266
+ severity="medium",
267
+ )
268
+ )
269
+
270
+ def _check_comment(
271
+ self, node: Any, buf: bytes, source: str, file_path: str, findings: list[dict]
272
+ ) -> None:
273
+ line = node.start_point[0] + 1
274
+ text = _node_text(source, node)
275
+ if "@ts-ignore" in text:
276
+ findings.append(
277
+ make_finding(
278
+ finding_type="ts_ignore",
279
+ file=file_path,
280
+ line=line,
281
+ title="@ts-ignore suppression",
282
+ description="TypeScript compiler error suppressed with @ts-ignore",
283
+ evidence=text.strip(),
284
+ severity="medium",
285
+ )
286
+ )
287
+ elif "@ts-expect-error" in text:
288
+ findings.append(
289
+ make_finding(
290
+ finding_type="ts_ignore",
291
+ file=file_path,
292
+ line=line,
293
+ title="@ts-expect-error suppression",
294
+ description="TypeScript compiler error suppressed with @ts-expect-error",
295
+ evidence=text.strip(),
296
+ severity="medium",
297
+ )
298
+ )