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,558 @@
1
+ """Unified Dashboard — Single web entry point for Patchi.
2
+
3
+ Mission: v2 as base + v1 enhancements + full CLI wrapper.
4
+ - All v2 pages: brain-map, council, attack-timeline, live-tests, hosted
5
+ - v1 enhancements: brain map viz, health components, recent findings, cost alerts, project context
6
+ - Full CLI wrapper: every CLI command exposed via web UI/API
7
+ - Single design: one nav, one WebSocket (/ws), one template env (templates_v2)
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import json
13
+ import logging
14
+ from pathlib import Path
15
+
16
+ from fastapi import APIRouter, Request, WebSocket, WebSocketDisconnect
17
+ from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
18
+ from fastapi.templating import Jinja2Templates
19
+
20
+ from patchi.core import config as cfg
21
+ from patchi.core.ai.tools.executor import ToolExecutor, WebConfirmationProvider
22
+
23
+ router = APIRouter()
24
+
25
+ _log = logging.getLogger("patchi.web.dashboard")
26
+
27
+ templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates"))
28
+ templates.env.filters["tojson"] = lambda v: json.dumps(v)
29
+
30
+
31
+ # ──────────────────────────────────────────────────────────────────────
32
+ # Helpers
33
+ # ──────────────────────────────────────────────────────────────────────
34
+
35
+ def _safe_mode(root: Path) -> str:
36
+ try:
37
+ return cfg.load(root).get("mode", "confirm")
38
+ except Exception:
39
+ return "confirm"
40
+
41
+
42
+ def _collect_recent_findings(root: Path, limit: int = 10) -> list[dict]:
43
+ """Collect recent findings from all scanners."""
44
+ from patchi.core import memory as mem
45
+ scan_results = mem.get_scan_results(root)
46
+ findings = []
47
+ for scanner, data in scan_results.items():
48
+ for finding in data.get("findings", [])[-limit:]:
49
+ if isinstance(finding, dict):
50
+ finding["source_scanner"] = scanner
51
+ findings.append(finding)
52
+ findings.sort(key=lambda f: f.get("timestamp", ""), reverse=True)
53
+ return findings
54
+
55
+
56
+ def _get_brain_data(root: Path) -> dict:
57
+ """Get consolidated brain data for dashboard."""
58
+ from patchi.core import memory as mem
59
+ from patchi.core.health import compute as compute_health
60
+
61
+ brain = mem.get_brain(root)
62
+ hs = compute_health(root)
63
+
64
+ return {
65
+ "health_score": hs.total,
66
+ "health_grade": hs.grade,
67
+ "health_components": hs.to_dict().get("components", {}),
68
+ "file_count": brain.get("file_count", 0),
69
+ "route_count": brain.get("route_count", 0),
70
+ "framework": brain.get("framework", "Unknown"),
71
+ "languages": brain.get("languages", {}),
72
+ "project_purpose": brain.get("project_purpose", ""),
73
+ "project_domain": brain.get("project_domain", ""),
74
+ "active_security_domains": brain.get("active_security_domains", []),
75
+ "recent_findings": _collect_recent_findings(root, 10),
76
+ "stale": brain.get("stale", False),
77
+ "last_scan": brain.get("last_scan", ""),
78
+ }
79
+
80
+
81
+ # ──────────────────────────────────────────────────────────────────────
82
+ # Main Dashboard (/)
83
+ # ──────────────────────────────────────────────────────────────────────
84
+
85
+ @router.get("/", response_class=HTMLResponse)
86
+ async def dashboard(request: Request):
87
+ """Unified Mission Control — single entry point at /."""
88
+ root = request.app.state.root
89
+
90
+ cost_alert = None
91
+ try:
92
+ from patchi.core.tenant import check_tenant_cost_alert
93
+ cost_alert = check_tenant_cost_alert(root, cfg.load(root))
94
+ except Exception as _exc:
95
+ _log.debug('suppressed: %s', _exc)
96
+
97
+ import asyncio as _asyncio
98
+ brain_data = await _asyncio.to_thread(_get_brain_data, request.app.state.root)
99
+
100
+ return templates.TemplateResponse(
101
+ request,
102
+ "dashboard.html",
103
+ {
104
+ "request": request,
105
+ **brain_data,
106
+ "cost_alert": cost_alert,
107
+ "mode": _safe_mode(request.app.state.root),
108
+ },
109
+ )
110
+
111
+
112
+ @router.get("/landing", response_class=HTMLResponse)
113
+ async def landing_page(request: Request):
114
+ """Public landing / marketing page (templates/landing.html)."""
115
+ root = request.app.state.root
116
+ brain = _get_brain_data(root)
117
+ brain["active_domains"] = brain.get("active_security_domains", [])
118
+ return templates.TemplateResponse(
119
+ request,
120
+ "landing.html",
121
+ {"request": request, **brain},
122
+ )
123
+
124
+
125
+ @router.get("/doctor", response_class=HTMLResponse)
126
+ async def doctor_page(request: Request):
127
+ """Web front-end for `p doctor` — shows environment diagnostics."""
128
+ from patchi.web.api_legacy import web_doctor
129
+
130
+ resp = await web_doctor(request)
131
+ data = json.loads(resp.body)
132
+ checks = data.get("checks", [])
133
+ errors = data.get("errors", 0)
134
+
135
+ rows = []
136
+ for c in checks:
137
+ badge = "ok" if c.get("ok") else "fail"
138
+ rows.append(
139
+ f"<tr><td>{c.get('label','')}</td>"
140
+ f"<td class='badge {badge}'>{'OK' if c.get('ok') else 'FAIL'}</td>"
141
+ f"<td>{c.get('note','')}</td></tr>"
142
+ )
143
+ rows_html = "\n".join(rows)
144
+
145
+ html = f"""<!doctype html>
146
+ <html lang="en"><head><meta charset="utf-8">
147
+ <meta name="viewport" content="width=device-width, initial-scale=1">
148
+ <title>Patchi · Doctor</title>
149
+ <style>
150
+ body {{ font-family: ui-sans-serif, system-ui, sans-serif; margin: 0; background: #0d1117; color: #e6edf3; padding: 2rem; }}
151
+ h1 {{ font-size: 1.6rem; }} .wrap {{ max-width: 720px; margin: 0 auto; }}
152
+ table {{ width: 100%; border-collapse: collapse; margin-top: 1rem; }}
153
+ td {{ padding: .6rem .8rem; border-bottom: 1px solid #21262d; }}
154
+ .badge {{ font-weight: 600; }}
155
+ .badge.ok {{ color: #3fb950; }} .badge.fail {{ color: #f85149; }}
156
+ .summary {{ margin-top: 1rem; font-weight: 600; }}
157
+ a {{ color: #58a6ff; }}
158
+ </style></head>
159
+ <body><div class="wrap">
160
+ <h1>Patchi Doctor</h1>
161
+ <p>Environment diagnostics for this Patchi installation.</p>
162
+ <table>{rows_html}</table>
163
+ <div class="summary">{'All checks passed.' if errors == 0 else str(errors) + ' check(s) failed.'}</div>
164
+ <p><a href="/">← Back to Mission Control</a></p>
165
+ </div></body></html>"""
166
+ return HTMLResponse(html)
167
+
168
+
169
+ # ──────────────────────────────────────────────────────────────────────
170
+ # HTML Page Routes
171
+ # ──────────────────────────────────────────────────────────────────────
172
+
173
+ @router.get("/brain-map", response_class=HTMLResponse)
174
+ async def brain_map_page(request: Request):
175
+ """Brain map visualization with full import graph."""
176
+ root = request.app.state.root
177
+ from patchi.core import memory as mem
178
+ layers_data = mem.get_layers(root)
179
+ return templates.TemplateResponse(
180
+ request, "brain.html", {"request": request, "layers_data": layers_data}
181
+ )
182
+
183
+
184
+ @router.get("/council", response_class=HTMLResponse)
185
+ async def council_page(request: Request):
186
+ """Council deliberation view with session history."""
187
+ root = request.app.state.root
188
+ from patchi.core import memory as mem
189
+ council_sessions = mem.read(mem.MemoryCategory.ISSUES, root)
190
+ council_sessions = [s for s in council_sessions if s.get("type") == "council_session"]
191
+ return templates.TemplateResponse(
192
+ request, "council.html", {"request": request, "council_sessions": council_sessions[-20:]}
193
+ )
194
+
195
+
196
+ @router.get("/attack-timeline", response_class=HTMLResponse)
197
+ async def attack_timeline_page(request: Request):
198
+ """Attack simulation timeline (Red Team results)."""
199
+ root = request.app.state.root
200
+ from patchi.core import memory as mem
201
+ scan_results = mem.get_scan_results(root)
202
+ attack_data = scan_results.get("RedTeamEngineAgent", {})
203
+ return templates.TemplateResponse(
204
+ request, "attacks.html", {"request": request, "attack_data": attack_data}
205
+ )
206
+
207
+
208
+ @router.get("/live-tests", response_class=HTMLResponse)
209
+ async def live_tests_page(request: Request):
210
+ """Live test session monitor."""
211
+ root = request.app.state.root
212
+ from patchi.core import memory as mem
213
+ scan_results = mem.get_scan_results(root)
214
+ test_data = scan_results.get("TestRunner", {})
215
+ return templates.TemplateResponse(
216
+ request, "live_testing.html", {"request": request, "test_data": test_data}
217
+ )
218
+
219
+
220
+ @router.get("/hosted", response_class=HTMLResponse)
221
+ async def hosted_page(request: Request):
222
+ """Hosted mode dashboard (experimental)."""
223
+ root = request.app.state.root
224
+ from patchi.core import memory as mem
225
+ scan_results = mem.get_scan_results(root)
226
+ hosted_data = scan_results.get("HostedModeAgent", {})
227
+ return templates.TemplateResponse(
228
+ request, "hosted.html", {"request": request, "hosted_data": hosted_data}
229
+ )
230
+
231
+
232
+ # ──────────────────────────────────────────────────────────────────────
233
+ # CLI Wrapper API Routes — Every CLI command exposed
234
+ # ──────────────────────────────────────────────────────────────────────
235
+
236
+ @router.get("/api/cli/commands")
237
+ async def list_cli_commands(request: Request):
238
+ """List all available CLI commands for web exposure."""
239
+ from patchi.cli.registry import COMMANDS as REGISTRY_COMMANDS
240
+ commands = []
241
+ for cmd in REGISTRY_COMMANDS:
242
+ commands.append({
243
+ "name": cmd.name,
244
+ "help": cmd.help,
245
+ "args": [{"name": a.name, "help": a.help, "required": not a.nargs or a.nargs != "?"} for a in cmd.args],
246
+ "subcommands": [{"name": sc.name, "help": sc.help} for sc in cmd.subcommands] if cmd.subcommands else [],
247
+ })
248
+ return {"commands": commands}
249
+
250
+
251
+ @router.get("/api/v2/agents/stream")
252
+ async def agents_stream(request: Request):
253
+ """Server-sent events stream for live agent progress."""
254
+ root = request.app.state.root
255
+ from patchi.core import memory as mem
256
+
257
+ async def event_generator():
258
+ scan_results = mem.get_scan_results(root)
259
+ for scanner, data in scan_results.items():
260
+ yield f"data: {json.dumps({'agent': scanner, 'status': 'completed', 'findings': len(data.get('findings', []))})}\n\n"
261
+ await asyncio.sleep(0.1)
262
+
263
+ return StreamingResponse(event_generator(), media_type="text/event-stream")
264
+
265
+
266
+ @router.get("/api/v2/brain/layers")
267
+ async def get_brain_layers(request: Request):
268
+ """Get brain layers for visualization."""
269
+ root = request.app.state.root
270
+ from patchi.core import memory as mem
271
+ layers_data = mem.get_layers(root)
272
+ return layers_data
273
+
274
+
275
+ @router.get("/api/v2/council/sessions")
276
+ async def get_council_sessions(request: Request):
277
+ """Get council session history."""
278
+ root = request.app.state.root
279
+ from patchi.core import memory as mem
280
+ history = mem.read(mem.MemoryCategory.ISSUES, root)
281
+ sessions = [s for s in history if s.get("type") == "council_session"]
282
+ return {"sessions": sessions[-50:]}
283
+
284
+
285
+ @router.get("/api/v2/attack/timeline")
286
+ async def get_attack_timeline(request: Request):
287
+ """Get attack simulation timeline."""
288
+ root = request.app.state.root
289
+ from patchi.core import memory as mem
290
+ scan_results = mem.get_scan_results(root)
291
+ attack_data = scan_results.get("RedTeamEngineAgent", {})
292
+ return attack_data
293
+
294
+
295
+ @router.get("/api/v2/live-tests/status")
296
+ async def get_live_test_status(request: Request):
297
+ """Get live test status."""
298
+ root = request.app.state.root
299
+ from patchi.core import memory as mem
300
+ scan_results = mem.get_scan_results(root)
301
+ test_data = scan_results.get("TestRunner", {})
302
+ return test_data
303
+
304
+
305
+ @router.post("/api/v2/tools/execute")
306
+ async def execute_tool(request: Request):
307
+ """Execute an AI tool call from dashboard."""
308
+ root = request.app.state.root
309
+ data = await request.json()
310
+ tool_name = data.get("tool")
311
+ parameters = data.get("parameters", {})
312
+
313
+ executor = ToolExecutor(root, confirmation_provider=WebConfirmationProvider())
314
+ result = await executor.execute(tool_name, parameters, invoked_by="dashboard")
315
+
316
+ return {
317
+ "success": result.success,
318
+ "result": result.result,
319
+ "error": result.error,
320
+ "invocation_id": result.invocation_id,
321
+ }
322
+
323
+
324
+ @router.get("/api/v2/tools/list")
325
+ async def list_tools(request: Request):
326
+ """List all available AI tools."""
327
+ from patchi.core.ai.tools.registry import get_tool_registry
328
+ registry = get_tool_registry()
329
+ category = request.query_params.get("category")
330
+ tools = registry.list_tools(category)
331
+ return {"tools": tools}
332
+
333
+
334
+ @router.post("/api/v2/council/deliberate")
335
+ async def deliberate(request: Request):
336
+ """Run a full Council deliberation on an issue (REST form of the WS action)."""
337
+ root = request.app.state.root
338
+ try:
339
+ body = await request.json()
340
+ except Exception:
341
+ return JSONResponse({"error": "JSON body required"}, status_code=400)
342
+
343
+ issue = (body.get("issue") or "").strip()
344
+ if not issue:
345
+ return JSONResponse({"error": "issue is required"}, status_code=400)
346
+
347
+ try:
348
+ from patchi.core.brain.council import run_council
349
+ session = await run_council(root, issue)
350
+ except Exception as e:
351
+ _log.error("Council deliberation failed: %s", e)
352
+ return JSONResponse({"error": f"Deliberation failed: {e}"}, status_code=500)
353
+
354
+ try:
355
+ from patchi.core import memory as mem
356
+ mem.save_issue(
357
+ {
358
+ "type": "council_session",
359
+ "issue": issue,
360
+ "synthesis": session.synthesis,
361
+ "consensus": session.consensus_reached,
362
+ "timestamp": session.started_at,
363
+ "decisions": [
364
+ {
365
+ "persona_name": d.persona_name,
366
+ "analysis": d.analysis,
367
+ "recommendation": d.recommendation,
368
+ "confidence": d.confidence,
369
+ }
370
+ for d in session.persona_decisions
371
+ ],
372
+ "action_plan": session.action_plan,
373
+ },
374
+ root,
375
+ )
376
+ except Exception as e:
377
+ _log.warning("Failed to persist council session: %s", e)
378
+
379
+ return {
380
+ "issue": issue,
381
+ "synthesis": session.synthesis,
382
+ "consensus": session.consensus_reached,
383
+ "action_plan": session.action_plan,
384
+ "decisions": [
385
+ {
386
+ "persona_name": d.persona_name,
387
+ "analysis": d.analysis,
388
+ "recommendation": d.recommendation,
389
+ "confidence": d.confidence,
390
+ }
391
+ for d in session.persona_decisions
392
+ ],
393
+ "duration_ms": session.duration_ms,
394
+ }
395
+
396
+
397
+ # ──────────────────────────────────────────────────────────────────────
398
+ # WebSocket
399
+ # ──────────────────────────────────────────────────────────────────────
400
+
401
+ @router.websocket("/ws")
402
+ async def websocket_endpoint(ws: WebSocket):
403
+ """Unified WebSocket for real-time dashboard updates."""
404
+ from patchi.web.ws import manager as ws_manager
405
+
406
+ await ws.accept()
407
+ await ws_manager.connect(ws)
408
+ root = ws.app.state.root
409
+
410
+ try:
411
+ await _send_initial_state(ws, root)
412
+ while True:
413
+ raw = await ws.receive_text()
414
+ await _handle_ws_message(ws, root, raw)
415
+ except WebSocketDisconnect:
416
+ pass
417
+ except Exception as e:
418
+ _log.error(f"WebSocket error: {e}")
419
+ finally:
420
+ await ws_manager.disconnect(ws)
421
+
422
+
423
+ async def _send_initial_state(ws: WebSocket, root: Path):
424
+ """Send initial dashboard state."""
425
+ from patchi.core import memory as mem
426
+ from patchi.core.health import compute as compute_health
427
+
428
+ brain = mem.get_brain(root)
429
+ hs = compute_health(root)
430
+
431
+ await ws.send_json(
432
+ {
433
+ "event": "initial_state",
434
+ "data": {
435
+ "health_score": hs.total,
436
+ "health_grade": hs.grade,
437
+ "health_components": hs.to_dict().get("components", {}),
438
+ "file_count": brain.get("file_count", 0),
439
+ "route_count": brain.get("route_count", 0),
440
+ "framework": brain.get("framework", "Unknown"),
441
+ "languages": brain.get("languages", {}),
442
+ "mode": _safe_mode(root),
443
+ },
444
+ }
445
+ )
446
+
447
+
448
+ async def _handle_ws_message(ws: WebSocket, root: Path, raw: str):
449
+ """Handle incoming WebSocket message."""
450
+ try:
451
+ msg = json.loads(raw)
452
+ except json.JSONDecodeError:
453
+ return
454
+
455
+ action = msg.get("action")
456
+ from patchi.core import memory as mem
457
+ if action == "subscribe":
458
+ await ws.send_json({"event": "subscribed", "data": {"channels": msg.get("data", {}).get("channels", [])}})
459
+ elif action == "execute_tool":
460
+ tool_name = msg.get("tool")
461
+ parameters = msg.get("parameters", {})
462
+ executor = ToolExecutor(root, confirmation_provider=WebConfirmationProvider())
463
+ exec_result = await executor.execute(tool_name, parameters, invoked_by="dashboard")
464
+ await ws.send_json(
465
+ {
466
+ "event": "tool_result",
467
+ "data": {
468
+ "tool": tool_name,
469
+ "success": exec_result.success,
470
+ "result": exec_result.result,
471
+ "error": exec_result.error,
472
+ },
473
+ }
474
+ )
475
+ elif action == "start_scan":
476
+ def _cli_scan():
477
+ from patchi.cli.commands.scan_cmd import run as cli_scan
478
+ cli_scan(root=root, quiet=True, no_logo=True)
479
+
480
+ try:
481
+ await asyncio.to_thread(_cli_scan)
482
+ brain_mem = mem.get_brain(root)
483
+ scans = mem.get_scan_results(root)
484
+ brain_meta = scans.get("Brain", {})
485
+ await ws.send_json(
486
+ {
487
+ "event": "scan_completed",
488
+ "data": {
489
+ "file_count": brain_mem.get("file_count", 0),
490
+ "route_count": brain_mem.get("route_count", 0),
491
+ "duration": brain_meta.get("duration", 0),
492
+ "findings": sum(len(d.get("findings", [])) for d in scans.values()),
493
+ },
494
+ }
495
+ )
496
+ except Exception as e:
497
+ _log.error("Scan failed: %s", e)
498
+ await ws.send_json({"event": "error", "data": {"message": str(e)}})
499
+ elif action == "run_fix":
500
+ # Execute fix command via CLI
501
+ def _cli_fix():
502
+ from patchi.cli.commands.fix_cmd import run as cli_fix
503
+ cli_fix(root=root, dry_run=False)
504
+
505
+ try:
506
+ await asyncio.to_thread(_cli_fix)
507
+ await ws.send_json({"event": "fix_completed", "data": {"success": True}})
508
+ except Exception as e:
509
+ _log.error("Fix failed: %s", e)
510
+ await ws.send_json({"event": "error", "data": {"message": str(e)}})
511
+ elif action == "run_council":
512
+ issue = msg.get("issue", "")
513
+ if issue:
514
+ try:
515
+ from patchi.core.brain.council import run_council
516
+ session = await run_council(root, issue)
517
+ await ws.send_json({
518
+ "event": "council_completed",
519
+ "data": {
520
+ "issue": issue,
521
+ "synthesis": session.synthesis,
522
+ "consensus": session.consensus_reached,
523
+ "action_plan": session.action_plan,
524
+ }
525
+ })
526
+ except Exception as e:
527
+ _log.error("Council failed: %s", e)
528
+ await ws.send_json({"event": "error", "data": {"message": str(e)}})
529
+ elif action == "status.request":
530
+ # Legacy base.html polls for current status on connect.
531
+ import time
532
+
533
+ try:
534
+ conf = cfg.load(root)
535
+ except Exception:
536
+ conf = {}
537
+ try:
538
+ brain = mem.get_brain(root)
539
+ except Exception:
540
+ brain = {}
541
+ await ws.send_json(
542
+ {
543
+ "event": "status",
544
+ "data": {
545
+ "connected": True,
546
+ "mode": conf.get("mode", "confirm"),
547
+ "project_purpose": brain.get("project_purpose", ""),
548
+ "timestamp": time.time(),
549
+ },
550
+ }
551
+ )
552
+
553
+
554
+ def _safe_mode(root: Path) -> str:
555
+ try:
556
+ return cfg.load(root).get("mode", "confirm")
557
+ except Exception:
558
+ return "confirm"