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,106 @@
1
+ """
2
+ RunAgent — starts the target app after P-Check green, for Test/Attack.
3
+
4
+ Chain: Check (Install/Build/Format) → green READY_TO_SERVE → Run (AppLauncher) → Test/Attack
5
+ Test/Attack agents rely on Run, Run relies on Check.
6
+
7
+ This agent is always run anytime a Test or Attack agent is queued.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+
14
+ from patchi.core.agents.base import (
15
+ AgentGroup,
16
+ AgentInput,
17
+ AgentResult,
18
+ AgentStatus,
19
+ BaseAgent,
20
+ Severity,
21
+ make_finding,
22
+ register,
23
+ )
24
+
25
+ _log = logging.getLogger("patchi.agents.run")
26
+
27
+
28
+ @register
29
+ class RunAgent(BaseAgent):
30
+ group = AgentGroup.SCANNER
31
+ name = "RunAgent"
32
+ description = "Run — start app on free port after P-Check green (test/attack dependency)"
33
+ timeout = 90
34
+
35
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
36
+ # Gate: Check must be green
37
+ try:
38
+ from patchi.core.testing.gate import require_ready
39
+
40
+ ready, url, st = require_ready(inp.root)
41
+ if not ready:
42
+ from patchi.core.testing.gate import gate_message
43
+
44
+ msg = gate_message(st)
45
+ result.status = AgentStatus.SKIPPED
46
+ result.data["gate_blocked"] = True
47
+ result.data["gate_reason"] = msg
48
+ result.add_finding(
49
+ make_finding(
50
+ severity=Severity.INFO,
51
+ file="",
52
+ line_start=0,
53
+ title="Run skipped — P-Check not READY_TO_SERVE",
54
+ description=msg,
55
+ finding_type="run_gate_blocked",
56
+ )
57
+ )
58
+ return
59
+ except Exception as exc: # noqa: BLE001
60
+ _log.debug("run gate check failed: %s", exc)
61
+
62
+ # Start app
63
+ try:
64
+ from patchi.core.testing.app_launcher import ensure_running
65
+
66
+ # Reuse if already running
67
+ url = ensure_running(inp.root, inp.config, inp.extra)
68
+ if url:
69
+ result.status = AgentStatus.SUCCEEDED
70
+ result.data["base_url"] = url
71
+ result.data["url"] = url
72
+ result.add_finding(
73
+ make_finding(
74
+ severity=Severity.INFO,
75
+ file="",
76
+ line_start=0,
77
+ title=f"App running at {url}",
78
+ description=f"RunAgent started target app at {url} (P-Check was green)",
79
+ finding_type="app_running",
80
+ )
81
+ )
82
+ else:
83
+ result.status = AgentStatus.FAILED
84
+ result.add_finding(
85
+ make_finding(
86
+ severity=Severity.HIGH,
87
+ file="",
88
+ line_start=0,
89
+ title="Run failed — could not start app",
90
+ description="AppLauncher ensure_running returned None — check .patchi/launcher/app.log and start command",
91
+ finding_type="run_failed",
92
+ )
93
+ )
94
+ except Exception as exc: # noqa: BLE001
95
+ _log.warning("RunAgent failed: %s", exc)
96
+ result.status = AgentStatus.FAILED
97
+ result.add_finding(
98
+ make_finding(
99
+ severity=Severity.HIGH,
100
+ file="",
101
+ line_start=0,
102
+ title="Run exception",
103
+ description=str(exc)[:300],
104
+ finding_type="run_exception",
105
+ )
106
+ )
@@ -0,0 +1,100 @@
1
+ """
2
+ RustUnwrapAgent §8.2.4 — clippy style unwrap/expect audit.
3
+
4
+ Finds .unwrap() / .expect() without descriptive message, suggests ?, expect("msg") or pattern match.
5
+ Optionally runs `cargo clippy -- -W clippy::unwrap_used` if cargo available.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import re
12
+ import shutil
13
+ import subprocess
14
+
15
+ from patchi.core.agents.base import (
16
+ AgentGroup,
17
+ AgentInput,
18
+ AgentResult,
19
+ AgentStatus,
20
+ BaseAgent,
21
+ Severity,
22
+ make_finding,
23
+ register,
24
+ safe_rglob,
25
+ )
26
+
27
+ _log = logging.getLogger("patchi.agents.rust_unwrap")
28
+
29
+ _UNWRAP_RE = re.compile(r"\.unwrap\(\)")
30
+ _EXPECT_RE = re.compile(r"\.expect\(\s*\"\"|\.expect\(\s*''|\\.expect\(\s*\)")
31
+
32
+
33
+ @register
34
+ class RustUnwrapAgent(BaseAgent):
35
+ group = AgentGroup.SCANNER
36
+ name = "RustUnwrapAgent"
37
+ description = "Rust unwrap()/expect() audit §8.2.4 — clippy unwrap_used"
38
+ timeout = 60
39
+
40
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
41
+ # Try clippy
42
+ if shutil.which("cargo"):
43
+ try:
44
+ proc = subprocess.run(
45
+ ["cargo", "clippy", "--", "-W", "clippy::unwrap_used", "-W", "clippy::expect_used"],
46
+ capture_output=True,
47
+ text=True,
48
+ timeout=30,
49
+ cwd=str(inp.root),
50
+ )
51
+ for line in (proc.stderr + proc.stdout).splitlines():
52
+ if "unwrap_used" in line or "expect_used" in line:
53
+ # clippy: file:line:col: warning: used `unwrap()`...
54
+ m = re.match(r"([^:]+):(\d+):\d+:\s*warning:\s*(.*)", line)
55
+ if m:
56
+ result.add_finding(
57
+ make_finding(
58
+ severity=Severity.LOW,
59
+ file=m.group(1),
60
+ line_start=int(m.group(2)),
61
+ title="clippy::unwrap_used",
62
+ description=m.group(3).strip()[:200],
63
+ finding_type="rust_unwrap",
64
+ )
65
+ )
66
+ except Exception as exc: # noqa: BLE001
67
+ _log.debug("clippy failed: %s", exc)
68
+
69
+ for fp in safe_rglob(inp.root, "*.rs"):
70
+ rel = fp.relative_to(inp.root).as_posix()
71
+ try:
72
+ txt = fp.read_text(encoding="utf-8", errors="replace")
73
+ except OSError:
74
+ continue
75
+ for i, line in enumerate(txt.splitlines(), 1):
76
+ if _UNWRAP_RE.search(line):
77
+ result.add_finding(
78
+ make_finding(
79
+ severity=Severity.LOW,
80
+ file=rel,
81
+ line_start=i,
82
+ title="Unsafe .unwrap() — panics on Err/None",
83
+ description="Prefer `?`, `unwrap_or`, or `.expect(\"context\")` with message. Clippy: unwrap_used.",
84
+ evidence=line.strip()[:120],
85
+ finding_type="rust_unwrap",
86
+ )
87
+ )
88
+ elif _EXPECT_RE.search(line):
89
+ result.add_finding(
90
+ make_finding(
91
+ severity=Severity.LOW,
92
+ file=rel,
93
+ line_start=i,
94
+ title="Empty .expect(\"\") — no context",
95
+ description="`.expect(\"\")` gives no diagnostic; use descriptive message.",
96
+ evidence=line.strip()[:120],
97
+ finding_type="rust_expect_empty",
98
+ )
99
+ )
100
+ result.status = AgentStatus.SUCCEEDED
@@ -0,0 +1,284 @@
1
+ """SBOMGeneratorAgent — generates CycloneDX/SPDX Software Bill of Materials.
2
+
3
+ Covers §11.5:
4
+ - Parse dependency manifests (package.json, requirements.txt, Cargo.toml, go.mod, Gemfile, etc.)
5
+ - Generate CycloneDX 1.5-compatible JSON output
6
+ - Write to .patchi/sbom.cdx.json
7
+
8
+ Language-agnostic: supports npm, pip, cargo, go, bundler, composer ecosystems.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import logging
14
+ import re
15
+ from datetime import UTC, datetime
16
+ from pathlib import Path
17
+ from uuid import uuid4
18
+
19
+ from .base import (
20
+ AgentGroup,
21
+ AgentInput,
22
+ AgentResult,
23
+ AgentStatus,
24
+ BaseAgent,
25
+ Severity,
26
+ make_finding,
27
+ register,
28
+ safe_rglob,
29
+ )
30
+
31
+ _ECOSYSTEM_MAP = {
32
+ "package.json": ("npm", "JavaScript"),
33
+ "requirements.txt": ("pypi", "Python"),
34
+ "pyproject.toml": ("pypi", "Python"),
35
+ "Pipfile": ("pypi", "Python"),
36
+ "poetry.lock": ("pypi", "Python"),
37
+ "Cargo.toml": ("crates", "Rust"),
38
+ "Cargo.lock": ("crates", "Rust"),
39
+ "go.mod": ("go", "Go"),
40
+ "go.sum": ("go", "Go"),
41
+ "Gemfile": ("gem", "Ruby"),
42
+ "Gemfile.lock": ("gem", "Ruby"),
43
+ "composer.json": ("composer", "PHP"),
44
+ "yarn.lock": ("npm", "JavaScript"),
45
+ "pnpm-lock.yaml": ("npm", "JavaScript"),
46
+ }
47
+
48
+ _COMPONENT_TYPES = {
49
+ "npm": "library",
50
+ "pypi": "library",
51
+ "crates": "library",
52
+ "go": "library",
53
+ "gem": "library",
54
+ "composer": "library",
55
+ }
56
+
57
+
58
+ _log = logging.getLogger("patchi.agents.sbom_generator")
59
+
60
+
61
+ def _parse_deps(fp: Path) -> list[dict]:
62
+ deps: list[dict] = []
63
+ try:
64
+ content = fp.read_text(encoding="utf-8")
65
+ except Exception as e:
66
+ _log.warning("_parse_deps failed: %s", e)
67
+ return deps
68
+
69
+ if fp.name == "package.json":
70
+ try:
71
+ data = json.loads(content)
72
+ all_deps = {**data.get("dependencies", {}), **data.get("devDependencies", {})}
73
+ for name, ver in all_deps.items():
74
+ deps.append({"name": name, "version": ver.lstrip("^~>=<")})
75
+ except json.JSONDecodeError:
76
+ pass
77
+ elif fp.name == "requirements.txt":
78
+ for line in content.splitlines():
79
+ line = line.strip()
80
+ if not line or line.startswith("#") or line.startswith("-"):
81
+ continue
82
+ for sep in ["==", ">=", "<="]:
83
+ if sep in line:
84
+ name, ver = line.split(sep, 1)
85
+ deps.append({"name": name.strip(), "version": ver.strip()})
86
+ break
87
+ else:
88
+ deps.append({"name": line, "version": "unknown"})
89
+ elif fp.name == "Cargo.toml":
90
+ in_deps = False
91
+ for line in content.splitlines():
92
+ stripped = line.strip()
93
+ if stripped.startswith("[dependencies]") or stripped.startswith("[dev-dependencies]"):
94
+ in_deps = True
95
+ continue
96
+ if stripped.startswith("[") and in_deps:
97
+ in_deps = False
98
+ if in_deps and "=" in stripped:
99
+ m = re.match(r"(\w[\w\-_]*)", stripped)
100
+ if m:
101
+ name = m.group(1)
102
+ ver_part = stripped[len(name) :].strip("= ").strip('"')
103
+ deps.append({"name": name, "version": ver_part})
104
+ elif fp.name == "pyproject.toml":
105
+ in_deps = False
106
+ for line in content.splitlines():
107
+ stripped = line.strip()
108
+ if stripped.startswith("dependencies = ["):
109
+ in_deps = True
110
+ continue
111
+ if in_deps:
112
+ if stripped == "]":
113
+ in_deps = False
114
+ else:
115
+ m = re.match(r'["\']([^"\'>]+?)([>=<~!]+)([^"\']+)["\'],?', stripped)
116
+ if m:
117
+ deps.append({"name": m.group(1), "version": m.group(3)})
118
+ elif fp.name == "go.mod":
119
+ for line in content.splitlines():
120
+ stripped = line.strip()
121
+ if stripped.startswith("require ") or stripped.startswith("\trequire "):
122
+ parts = stripped.replace("require ", "").split()
123
+ if len(parts) >= 2:
124
+ deps.append({"name": parts[0], "version": parts[1]})
125
+ return deps
126
+
127
+
128
+ def _build_cyclonedx(
129
+ root: Path,
130
+ deps_by_eco: dict[str, list[dict]],
131
+ ) -> dict:
132
+ components: list[dict] = []
133
+ for eco, dep_list in deps_by_eco.items():
134
+ comp_type = _COMPONENT_TYPES.get(eco, "library")
135
+ for dep in dep_list:
136
+ components.append(
137
+ {
138
+ "type": comp_type,
139
+ "name": dep["name"],
140
+ "version": dep["version"],
141
+ "purl": f"pkg:{eco}/{dep['name']}@{dep['version']}",
142
+ "bom-ref": f"pkg:{eco}/{dep['name']}@{dep['version']}",
143
+ }
144
+ )
145
+
146
+ return {
147
+ "bomFormat": "CycloneDX",
148
+ "specVersion": "1.5",
149
+ "serialNumber": f"urn:uuid:{uuid4().hex}",
150
+ "version": 1,
151
+ "metadata": {
152
+ "timestamp": datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
153
+ "tools": [{"name": "Patchi", "version": "1.0"}],
154
+ "component": {
155
+ "type": "application",
156
+ "name": root.name,
157
+ },
158
+ },
159
+ "components": components,
160
+ }
161
+
162
+
163
+ @register
164
+ class SBOMGeneratorAgent(BaseAgent):
165
+ """Generates CycloneDX Software Bill of Materials from dependency manifests."""
166
+
167
+ group = AgentGroup.SCANNER
168
+ name = "SBOMGeneratorAgent"
169
+ description = "Generate CycloneDX SBOM from package.json, requirements.txt, Cargo.toml, go.mod, Gemfile, etc."
170
+
171
+ def _try_external(self, inp: AgentInput) -> dict | None:
172
+ """Try cdxgen then syft as subprocess — 20+ ecos depth, fail-open to internal parser."""
173
+ import shutil
174
+ import subprocess as _sp
175
+
176
+ # cdxgen: npx @cyclonedx/cdxgen -o /tmp/sbom.json
177
+ for cmd in (
178
+ ["npx", "--yes", "@cyclonedx/cdxgen", "-o", str(inp.root / ".patchi" / "sbom.cdxgen.json"), "--no-recurse"],
179
+ ["syft", str(inp.root), "-o", "cyclonedx-json"],
180
+ ):
181
+ if not shutil.which(cmd[0]):
182
+ continue
183
+ try:
184
+ proc = _sp.run(cmd, capture_output=True, text=True, timeout=120, cwd=str(inp.root))
185
+ out = proc.stdout.strip()
186
+ # cdxgen writes file, syft prints json
187
+ cand = inp.root / ".patchi" / "sbom.cdxgen.json"
188
+ if cand.exists():
189
+ return json.loads(cand.read_text(encoding="utf-8"))
190
+ if out and out.startswith("{"):
191
+ return json.loads(out)
192
+ except Exception as e:
193
+ _log.debug("SBOM external %s failed: %s", cmd[0], e)
194
+ return None
195
+
196
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
197
+ # Try external depth first
198
+ ext = self._try_external(inp)
199
+ if ext and ext.get("components"):
200
+ sbom = ext
201
+ total = len(ext.get("components", []))
202
+ sbom_dir = inp.root / ".patchi"
203
+ sbom_dir.mkdir(parents=True, exist_ok=True)
204
+ sbom_path = sbom_dir / "sbom.cdx.json"
205
+ try:
206
+ sbom_path.write_text(json.dumps(sbom, indent=2), encoding="utf-8")
207
+ result.findings.append(
208
+ make_finding(
209
+ self.name, "sbom_generated", Severity.INFO, str(sbom_path.relative_to(inp.root)), f"SBOM (external) {total} components"
210
+ )
211
+ )
212
+ except Exception as e:
213
+ result.add_error(f"Failed to write SBOM: {e}")
214
+ result.data["sbom_path"] = str(sbom_path)
215
+ result.data["total_components"] = total
216
+ result.data["ecosystems"] = ["external"]
217
+ result.data["sbom"] = sbom
218
+ result.data["external"] = True
219
+ result.status = AgentStatus.DONE
220
+ return
221
+
222
+ deps_by_eco: dict[str, list[dict]] = {}
223
+ files_scanned = 0
224
+
225
+ for manifest in _ECOSYSTEM_MAP:
226
+ for fp in safe_rglob(inp.root, manifest):
227
+ rel = fp.relative_to(inp.root).as_posix()
228
+ eco, _ = _ECOSYSTEM_MAP[manifest]
229
+ deps = _parse_deps(fp)
230
+ if deps:
231
+ deps_by_eco.setdefault(eco, []).extend(deps)
232
+ result.findings.append(
233
+ make_finding(
234
+ self.name,
235
+ "sbom_source",
236
+ Severity.INFO,
237
+ rel,
238
+ f"SBOM source: {rel} ({len(deps)} deps)",
239
+ )
240
+ )
241
+ files_scanned += 1
242
+
243
+ if not deps_by_eco:
244
+ result.findings.append(
245
+ make_finding(
246
+ self.name,
247
+ "no_sbom_sources",
248
+ Severity.INFO,
249
+ "",
250
+ "No dependency manifests found — cannot generate SBOM",
251
+ )
252
+ )
253
+ result.status = AgentStatus.DONE
254
+ return
255
+
256
+ sbom = _build_cyclonedx(inp.root, deps_by_eco)
257
+ total_deps = sum(len(d) for d in deps_by_eco.values())
258
+
259
+ # Write SBOM to .patchi/ directory
260
+ sbom_dir = inp.root / ".patchi"
261
+ sbom_dir.mkdir(parents=True, exist_ok=True)
262
+ sbom_path = sbom_dir / "sbom.cdx.json"
263
+ try:
264
+ sbom_path.write_text(json.dumps(sbom, indent=2), encoding="utf-8")
265
+ result.findings.append(
266
+ make_finding(
267
+ self.name,
268
+ "sbom_generated",
269
+ Severity.INFO,
270
+ str(sbom_path.relative_to(inp.root)),
271
+ f"SBOM generated: {total_deps} components across {len(deps_by_eco)} ecosystems",
272
+ detail=str(sbom_path),
273
+ )
274
+ )
275
+ except Exception as e:
276
+ result.add_error(f"Failed to write SBOM: {e}")
277
+
278
+ result.data["sbom_path"] = str(sbom_path)
279
+ result.data["total_components"] = total_deps
280
+ result.data["ecosystems"] = list(deps_by_eco.keys())
281
+ result.data["sbom"] = sbom
282
+ result.files_scanned = files_scanned
283
+ if result.status == AgentStatus.RUNNING:
284
+ result.status = AgentStatus.DONE
@@ -0,0 +1,67 @@
1
+ """
2
+ All 20 scanner agents for Patchi.
3
+
4
+ This file has been split into separate modules for better maintainability:
5
+ - core_scanner.py
6
+ - side_file_scanner.py
7
+ - ui_scanner.py
8
+ - dependency_scanner.py
9
+ - test_scanner.py
10
+ - dead_code_scanner.py
11
+ - env_scanner.py
12
+ - route_graph_scanner.py
13
+ - type_scanner.py
14
+ - comment_scanner.py
15
+ - duplicate_scanner.py
16
+
17
+ Each agent inherits BaseAgent, implements _run(), and is registered
18
+ via the @register decorator so the coordinator can find it by name.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from .base import register
24
+ from .build_tool_validator import BuildToolValidatorAgent
25
+ from .cicd_generator import CICDGeneratorAgent
26
+ from .comment_scanner import CommentScanner
27
+ from .core_scanner import CoreScanner
28
+ from .coverage_prioritizer import CoveragePrioritizerAgent
29
+ from .dead_code_hygiene import DeadCodeHygieneAgent
30
+ from .dead_code_scanner import DeadCodeScanner
31
+ from .dependency_scanner import DependencyScanner
32
+ from .duplicate_scanner import DuplicateScanner
33
+ from .env_scanner import EnvScanner
34
+ from .license_compliance import LicenseComplianceAgent
35
+ from .refactoring_agent import RefactoringAgent
36
+ from .route_graph_scanner import RouteGraphScanner
37
+ from .sbom_generator import SBOMGeneratorAgent
38
+ from .side_file_scanner import SideFileScanner
39
+ from .snapshot_drift_detector import SnapshotDriftDetectorAgent
40
+ from .spa_route_inventory import SPARouteInventoryAgent
41
+ from .test_scanner import TestScanner
42
+ from .type_scanner import TypeScanner
43
+ from .ui_scanner import UIScanner
44
+
45
+ __all__ = [
46
+ "CoreScanner",
47
+ "SideFileScanner",
48
+ "UIScanner",
49
+ "DependencyScanner",
50
+ "TestScanner",
51
+ "DeadCodeScanner",
52
+ "DeadCodeHygieneAgent",
53
+ "RefactoringAgent",
54
+ "SBOMGeneratorAgent",
55
+ "CICDGeneratorAgent",
56
+ "SPARouteInventoryAgent",
57
+ "CoveragePrioritizerAgent",
58
+ "BuildToolValidatorAgent",
59
+ "LicenseComplianceAgent",
60
+ "SnapshotDriftDetectorAgent",
61
+ "EnvScanner",
62
+ "RouteGraphScanner",
63
+ "TypeScanner",
64
+ "CommentScanner",
65
+ "DuplicateScanner",
66
+ "register",
67
+ ]
@@ -0,0 +1 @@
1
+ """Side agents — preflight install/build/format before app run."""
@@ -0,0 +1,65 @@
1
+ """BuildAgent §p check — npm run build / tsc --noEmit / cargo check."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import shutil
7
+ import subprocess
8
+
9
+ from patchi.core.agents.base import (
10
+ AgentGroup,
11
+ AgentInput,
12
+ AgentResult,
13
+ AgentStatus,
14
+ BaseAgent,
15
+ Severity,
16
+ make_finding,
17
+ register,
18
+ )
19
+
20
+ _log = logging.getLogger("patchi.agents.side.build")
21
+
22
+ @register
23
+ class BuildAgent(BaseAgent):
24
+ group = AgentGroup.SCANNER
25
+ name = "BuildAgent"
26
+ description = "Build check before app run"
27
+ timeout = 120
28
+
29
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
30
+ root = inp.root
31
+ findings = []
32
+ logs = []
33
+ cmds = []
34
+ if (root / "package.json").exists():
35
+ try:
36
+ import json
37
+
38
+ pkg = json.loads((root / "package.json").read_text(encoding="utf-8"))
39
+ if "build" in pkg.get("scripts", {}):
40
+ cmds.append((["npm", "run", "build"], "package.json"))
41
+ if (root / "tsconfig.json").exists() and shutil.which("npx"):
42
+ cmds.append((["npx", "tsc", "--noEmit"], "tsconfig.json"))
43
+ except Exception as exc: # noqa: BLE001
44
+ _log.debug("build pkg read failed: %s", exc)
45
+ if (root / "Cargo.toml").exists() and shutil.which("cargo"):
46
+ cmds.append((["cargo", "check"], "Cargo.toml"))
47
+ for cmd, file in cmds:
48
+ if not shutil.which(cmd[0]):
49
+ continue
50
+ try:
51
+ proc = subprocess.run(cmd, capture_output=True, text=True, timeout=60, cwd=str(root))
52
+ logs.append(f"$ {' '.join(cmd)}\n{proc.stdout[:500]}{proc.stderr[:500]}")
53
+ if proc.returncode != 0:
54
+ findings.append(make_finding(severity=Severity.HIGH, file=file, line_start=0, title=f"Build failed: {' '.join(cmd)}", description=(proc.stdout + proc.stderr)[:600], finding_type="build_failed"))
55
+ except Exception as exc: # noqa: BLE001
56
+ _log.debug("build %s failed: %s", cmd, exc)
57
+ result.status = AgentStatus.SUCCEEDED
58
+ result.findings = findings
59
+ result.data["build_logs"] = logs[:4]
60
+ out = root / ".patchi" / "launcher" / "build.log"
61
+ out.parent.mkdir(parents=True, exist_ok=True)
62
+ try:
63
+ out.write_text("\n".join(logs)[:8000], encoding="utf-8")
64
+ except OSError:
65
+ pass
@@ -0,0 +1,50 @@
1
+ """FormatAgent §p check — ruff / eslint / prettier --check."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import shutil
7
+ import subprocess
8
+
9
+ from patchi.core.agents.base import (
10
+ AgentGroup,
11
+ AgentInput,
12
+ AgentResult,
13
+ AgentStatus,
14
+ BaseAgent,
15
+ Severity,
16
+ make_finding,
17
+ register,
18
+ )
19
+
20
+ _log = logging.getLogger("patchi.agents.side.format")
21
+
22
+ @register
23
+ class FormatAgent(BaseAgent):
24
+ group = AgentGroup.SCANNER
25
+ name = "FormatAgent"
26
+ description = "Format check before app run"
27
+ timeout = 60
28
+
29
+ def _run(self, inp: AgentInput, result: AgentResult) -> None:
30
+ root = inp.root
31
+ findings = []
32
+ checks = [
33
+ (["ruff", "check", "."], "ruff"),
34
+ (["npx", "eslint", ".", "--max-warnings", "0"], "eslint"),
35
+ (["npx", "prettier", "--check", "."], "prettier"),
36
+ ]
37
+ for cmd, name in checks:
38
+ if not shutil.which(cmd[0]):
39
+ continue
40
+ try:
41
+ proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30, cwd=str(root))
42
+ if proc.returncode != 0 and proc.stdout.strip():
43
+ # only first 3 lines to avoid flood
44
+ snippet = "\n".join(proc.stdout.splitlines()[:3])[:400]
45
+ findings.append(make_finding(severity=Severity.LOW, file="", line_start=0, title=f"Format: {name} failed", description=snippet, finding_type="format_error"))
46
+ break # one format failure enough
47
+ except Exception as exc: # noqa: BLE001
48
+ _log.debug("format %s failed: %s", name, exc)
49
+ result.status = AgentStatus.SUCCEEDED
50
+ result.findings = findings