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,28 @@
1
+ """
2
+ Team Collaboration §10.3.1-4 — CODEOWNERS routing, knowledge base, gamification.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from pathlib import Path
8
+
9
+ def route_to_team(root: Path, finding: dict) -> str:
10
+ co=root/".github/CODEOWNERS"
11
+ if co.exists():
12
+ for line in co.read_text(encoding="utf-8", errors="replace").splitlines():
13
+ if line.strip().startswith("#") or not line.strip(): continue
14
+ parts=line.split()
15
+ if len(parts)>=2 and finding.get("file","").startswith(parts[0].lstrip("/")):
16
+ return parts[-1]
17
+ return "unassigned"
18
+
19
+ def leaderboard(root: Path) -> list[dict]:
20
+ try:
21
+ # count fixes per author via git log
22
+ import subprocess
23
+ out=subprocess.run(["git","log","--pretty=format:%an","--since=1.month.ago"], capture_output=True, text=True, timeout=5, cwd=str(root))
24
+ from collections import Counter
25
+ cnt=Counter(out.stdout.splitlines()) if out.returncode==0 else Counter()
26
+ return [{"author": a, "fixes": c} for a,c in cnt.most_common(5)]
27
+ except Exception:
28
+ return []
@@ -0,0 +1,132 @@
1
+ """
2
+ Structured logging for agent execution traces.
3
+
4
+ Provides a lightweight trace logger that records:
5
+ - Agent name, start/end time, duration
6
+ - Input file count, finding count
7
+ - Success/failure status
8
+ - Error messages
9
+
10
+ Uses loguru if available, falls back to stdlib logging.
11
+
12
+ Usage:
13
+ from patchi.core.brain.trace_log import trace_agent
14
+ with trace_agent("InjectionAgent", root=path) as trace:
15
+ result = AgentResult()
16
+ agent._run(inp, result)
17
+ trace.findings = len(result.findings)
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import time
23
+ from contextlib import contextmanager
24
+ from dataclasses import dataclass, field
25
+ from pathlib import Path
26
+ from typing import Any
27
+
28
+ try:
29
+ from loguru import logger as _logger
30
+
31
+ _HAS_LOGURU = True
32
+ except ImportError:
33
+ import logging
34
+
35
+ _logger = logging.getLogger("patchi.trace")
36
+ _HAS_LOGURU = False
37
+
38
+
39
+ @dataclass
40
+ class AgentTrace:
41
+ """Record of a single agent execution."""
42
+
43
+ agent_name: str
44
+ root: Path
45
+ start_time: float = 0.0
46
+ end_time: float = 0.0
47
+ duration_ms: int = 0
48
+ files_scanned: int = 0
49
+ findings: int = 0
50
+ status: str = "ok"
51
+ error: str = ""
52
+ metadata: dict[str, Any] = field(default_factory=dict)
53
+
54
+ def to_dict(self) -> dict:
55
+ return {
56
+ "agent": self.agent_name,
57
+ "root": str(self.root),
58
+ "duration_ms": self.duration_ms,
59
+ "files_scanned": self.files_scanned,
60
+ "findings": self.findings,
61
+ "status": self.status,
62
+ "error": self.error,
63
+ **self.metadata,
64
+ }
65
+
66
+
67
+ _TRACE_LOG: list[AgentTrace] = []
68
+
69
+
70
+ def get_trace_log() -> list[AgentTrace]:
71
+ """Return all recorded traces."""
72
+ return _TRACE_LOG
73
+
74
+
75
+ def clear_trace_log() -> None:
76
+ """Clear the trace log."""
77
+ _TRACE_LOG.clear()
78
+
79
+
80
+ @contextmanager
81
+ def trace_agent(agent_name: str, root: Path):
82
+ """Context manager that traces an agent execution."""
83
+ trace = AgentTrace(agent_name=agent_name, root=root)
84
+ trace.start_time = time.perf_counter()
85
+ _TRACE_LOG.append(trace)
86
+
87
+ try:
88
+ yield trace
89
+ except Exception as e:
90
+ trace.status = "error"
91
+ trace.error = str(e)
92
+ raise
93
+ finally:
94
+ trace.end_time = time.perf_counter()
95
+ trace.duration_ms = int((trace.end_time - trace.start_time) * 1000)
96
+
97
+ if _HAS_LOGURU:
98
+ _logger.debug(
99
+ "agent_trace | {agent} | {duration}ms | {findings} findings | {status}",
100
+ agent=trace.agent_name,
101
+ duration=trace.duration_ms,
102
+ findings=trace.findings,
103
+ status=trace.status,
104
+ )
105
+ else:
106
+ _logger.debug(
107
+ "agent_trace | %s | %dms | %d findings | %s",
108
+ trace.agent_name,
109
+ trace.duration_ms,
110
+ trace.findings,
111
+ trace.status,
112
+ )
113
+
114
+
115
+ def format_trace_summary() -> str:
116
+ """Format a summary of all traces as a table string."""
117
+ if not _TRACE_LOG:
118
+ return "No agent traces recorded."
119
+
120
+ lines = ["Agent Trace Summary", "=" * 60]
121
+ total_ms = 0
122
+ total_findings = 0
123
+ for t in _TRACE_LOG:
124
+ total_ms += t.duration_ms
125
+ total_findings += t.findings
126
+ status_icon = "ok" if t.status == "ok" else "FAIL"
127
+ lines.append(
128
+ f" {t.agent_name:25s} {t.duration_ms:6d}ms {t.findings:3d} findings [{status_icon}]"
129
+ )
130
+ lines.append("-" * 60)
131
+ lines.append(f" {'TOTAL':25s} {total_ms:6d}ms {total_findings:3d} findings")
132
+ return "\n".join(lines)
@@ -0,0 +1,65 @@
1
+ """
2
+ Type checker registry — dispatches to per-language type checkers.
3
+
4
+ Usage:
5
+ from patchi.core.brain.type_checker import check_types
6
+ findings = check_types(source, lang, file_path)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ from ..languages import Lang
14
+
15
+
16
+ def check_types(source: str, lang: Lang, file_path: str) -> list[dict]:
17
+ """Run the type checker for the given language. Returns list of findings."""
18
+ checker = _get_checker(lang)
19
+ if checker is None:
20
+ return []
21
+ return checker.check(source, file_path)
22
+
23
+
24
+ def _get_checker(lang: Lang) -> Any | None:
25
+ if lang == Lang.TYPESCRIPT:
26
+ from .typescript import TypeScriptChecker
27
+
28
+ return TypeScriptChecker()
29
+ if lang == Lang.PYTHON:
30
+ from .python import PythonTypeChecker
31
+
32
+ return PythonTypeChecker()
33
+ if lang == Lang.JAVA:
34
+ from .java import JavaTypeChecker
35
+
36
+ return JavaTypeChecker()
37
+ if lang == Lang.GO:
38
+ from .go import GoTypeChecker
39
+
40
+ return GoTypeChecker()
41
+ if lang == Lang.RUST:
42
+ from .rust import RustTypeChecker
43
+
44
+ return RustTypeChecker()
45
+ if lang == Lang.C_SHARP:
46
+ from .csharp import CSharpTypeChecker
47
+
48
+ return CSharpTypeChecker()
49
+ if lang == Lang.KOTLIN:
50
+ from .kotlin import KotlinTypeChecker
51
+
52
+ return KotlinTypeChecker()
53
+ if lang == Lang.SWIFT:
54
+ from .swift import SwiftTypeChecker
55
+
56
+ return SwiftTypeChecker()
57
+ if lang == Lang.PHP:
58
+ from .php import PHPTypeChecker
59
+
60
+ return PHPTypeChecker()
61
+ if lang == Lang.DART:
62
+ from .dart import DartTypeChecker
63
+
64
+ return DartTypeChecker()
65
+ return None
@@ -0,0 +1,60 @@
1
+ """Base class for per-language type checkers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from abc import ABC, abstractmethod
7
+ from typing import Any
8
+
9
+ _log = logging.getLogger("patchi.brain.base")
10
+
11
+
12
+ class BaseTypeChecker(ABC):
13
+ """Abstract base for language-specific type checkers."""
14
+
15
+ language: str = ""
16
+
17
+ @abstractmethod
18
+ def check(self, source: str, file_path: str) -> list[dict]:
19
+ """Return list of type issue findings.
20
+ Each finding: {
21
+ "type": str, # e.g. "explicit_any", "missing_return_type"
22
+ "file": str,
23
+ "line": int,
24
+ "title": str,
25
+ "description": str,
26
+ "evidence": str,
27
+ "severity": str, # "low", "medium", "high"
28
+ }
29
+ """
30
+ ...
31
+
32
+
33
+ def _node_text(source: str, node: Any) -> str:
34
+ """Extract text from a tree-sitter node using the source string."""
35
+ try:
36
+ buf = source.encode("utf-8")
37
+ return buf[node.start_byte : node.end_byte].decode("utf-8", errors="replace")
38
+ except Exception as e:
39
+ _log.debug("_node_text failed: %s", e)
40
+ return ""
41
+
42
+
43
+ def make_finding(
44
+ finding_type: str,
45
+ file: str,
46
+ line: int,
47
+ title: str,
48
+ description: str,
49
+ evidence: str = "",
50
+ severity: str = "medium",
51
+ ) -> dict:
52
+ return {
53
+ "type": finding_type,
54
+ "file": file,
55
+ "line": line,
56
+ "title": title,
57
+ "description": description,
58
+ "evidence": evidence,
59
+ "severity": severity,
60
+ }
@@ -0,0 +1,59 @@
1
+ """C# 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.csharp")
12
+
13
+
14
+ class CSharpTypeChecker(BaseTypeChecker):
15
+ language = "c_sharp"
16
+
17
+ def check(self, source: str, file_path: str) -> list[dict]:
18
+ findings: list[dict] = []
19
+ parser = get_parser(Lang.C_SHARP)
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("CSharpTypeChecker.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 == "method_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, "predefined_type") or self._child_by_type(
39
+ node, "identifier"
40
+ )
41
+ if return_type and _node_text(source, return_type) in ("object", "dynamic"):
42
+ findings.append(
43
+ make_finding(
44
+ finding_type="untyped_return",
45
+ file=file_path,
46
+ line=line,
47
+ title=f"Method returns '{_node_text(source, return_type)}'",
48
+ description=f"Method '{name}' returns untyped '{_node_text(source, return_type)}' — 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,59 @@
1
+ """Dart 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.dart")
12
+
13
+
14
+ class DartTypeChecker(BaseTypeChecker):
15
+ language = "dart"
16
+
17
+ def check(self, source: str, file_path: str) -> list[dict]:
18
+ findings: list[dict] = []
19
+ parser = get_parser(Lang.DART)
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("DartTypeChecker.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 ("method_declaration", "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, "type_identifier") or self._child_by_type(
39
+ node, "generic_type"
40
+ )
41
+ if return_type and _node_text(source, return_type) == "dynamic":
42
+ findings.append(
43
+ make_finding(
44
+ finding_type="dynamic_type",
45
+ file=file_path,
46
+ line=line,
47
+ title="Function returns 'dynamic'",
48
+ description=f"Function '{name}' returns 'dynamic' — prefer a specific type for sound null safety",
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,81 @@
1
+ """Go 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.go")
12
+
13
+
14
+ class GoTypeChecker(BaseTypeChecker):
15
+ language = "go"
16
+
17
+ def check(self, source: str, file_path: str) -> list[dict]:
18
+ findings: list[dict] = []
19
+ parser = get_parser(Lang.GO)
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("GoTypeChecker.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_node = self._child_by_type(node, "identifier")
34
+ name = _node_text(source, name_node) if name_node else ""
35
+ result = self._child_by_type(node, "result")
36
+ if result is not None:
37
+ for child in result.children:
38
+ if (
39
+ child.type == "type_identifier"
40
+ and _node_text(source, child) == "interface{}"
41
+ ):
42
+ findings.append(
43
+ make_finding(
44
+ finding_type="empty_interface",
45
+ file=file_path,
46
+ line=line,
47
+ title="Function returns 'interface{}'",
48
+ description=f"Function '{name}' returns empty interface — consider a concrete type",
49
+ evidence=f"{name}: interface{{}}",
50
+ severity="medium",
51
+ )
52
+ )
53
+ break
54
+ params = self._child_by_type(node, "parameter_list")
55
+ if params:
56
+ for child in params.children:
57
+ if child.type == "parameter_declaration":
58
+ ptype = (
59
+ self._child_by_type(child, "type_identifier")
60
+ or self._child_by_type(child, "pointer_type")
61
+ or self._child_by_type(child, "generic_type")
62
+ )
63
+ if ptype and _node_text(source, ptype) == "interface{}":
64
+ findings.append(
65
+ make_finding(
66
+ finding_type="empty_interface",
67
+ file=file_path,
68
+ line=line,
69
+ title="Parameter is 'interface{}'",
70
+ description=f"Parameter in '{name}' is empty interface — consider a concrete type",
71
+ severity="medium",
72
+ )
73
+ )
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,75 @@
1
+ """Java 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.java")
12
+
13
+
14
+ class JavaTypeChecker(BaseTypeChecker):
15
+ language = "java"
16
+
17
+ def check(self, source: str, file_path: str) -> list[dict]:
18
+ findings: list[dict] = []
19
+ parser = get_parser(Lang.JAVA)
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("JavaTypeChecker.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 == "method_declaration":
32
+ line = node.start_point[0] + 1
33
+ name_node = self._child_by_type(node, "identifier")
34
+ name = _node_text(source, name_node) if name_node else ""
35
+ return_type_node = self._child_by_type(node, "type_identifier") or self._child_by_type(
36
+ node, "generic_type"
37
+ )
38
+ if return_type_node and _node_text(source, return_type_node) == "Object":
39
+ findings.append(
40
+ make_finding(
41
+ finding_type="explicit_object",
42
+ file=file_path,
43
+ line=line,
44
+ title="Method returns 'Object'",
45
+ description=f"Method '{name}' returns raw 'Object' type — consider generics",
46
+ evidence=f"{name}: Object",
47
+ severity="medium",
48
+ )
49
+ )
50
+ params = self._child_by_type(node, "formal_parameters")
51
+ if params:
52
+ for child in params.children:
53
+ if child.type == "formal_parameter":
54
+ ptype = self._child_by_type(
55
+ child, "type_identifier"
56
+ ) or self._child_by_type(child, "generic_type")
57
+ if ptype and _node_text(source, ptype) == "Object":
58
+ findings.append(
59
+ make_finding(
60
+ finding_type="explicit_object",
61
+ file=file_path,
62
+ line=line,
63
+ title="Parameter typed as 'Object'",
64
+ description=f"Parameter in '{name}' is raw 'Object' type",
65
+ severity="medium",
66
+ )
67
+ )
68
+ for child in node.children:
69
+ self._walk(child, source, file_path, findings)
70
+
71
+ def _child_by_type(self, node: Any, ntype: str) -> Any | None:
72
+ for child in node.children:
73
+ if child.type == ntype:
74
+ return child
75
+ return None
@@ -0,0 +1,75 @@
1
+ """Kotlin 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.kotlin")
12
+
13
+
14
+ class KotlinTypeChecker(BaseTypeChecker):
15
+ language = "kotlin"
16
+
17
+ def check(self, source: str, file_path: str) -> list[dict]:
18
+ findings: list[dict] = []
19
+ parser = get_parser(Lang.KOTLIN)
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("KotlinTypeChecker.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, "type_identifier") or self._child_by_type(
39
+ node, "user_type"
40
+ )
41
+ if return_type and _node_text(source, return_type) == "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
+ # Check for !! non-null assertions
53
+ body = self._child_by_type(node, "function_body") or self._child_by_type(node, "block")
54
+ if body:
55
+ body_text = _node_text(source, body)
56
+ if "!!" in body_text:
57
+ findings.append(
58
+ make_finding(
59
+ finding_type="non_null_assertion",
60
+ file=file_path,
61
+ line=line,
62
+ title="Non-null assertion '!!' used",
63
+ description=f"Function '{name}' uses '!!' — may throw NPE",
64
+ evidence="!!",
65
+ severity="medium",
66
+ )
67
+ )
68
+ for child in node.children:
69
+ self._walk(child, source, file_path, findings)
70
+
71
+ def _child_by_type(self, node: Any, ntype: str) -> Any | None:
72
+ for child in node.children:
73
+ if child.type == ntype:
74
+ return child
75
+ return None