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,597 @@
1
+ """
2
+ `p chat` — The Brain. The single intelligence that controls everything.
3
+
4
+ The chat IS the orchestrator. It has direct access to:
5
+ - Read/write any file in the project
6
+ - Run any CLI command
7
+ - Spawn any agent (security, testing, fixing, etc.)
8
+ - Access the full brain/memory state
9
+ - Make decisions based on context
10
+
11
+ Usage:
12
+ p chat — interactive brain session
13
+ p chat "scan this project" — executes security scan
14
+ p chat "what's in src/auth.py" — reads and explains the file
15
+ p chat "fix all critical findings" — scans and fixes
16
+ p chat "run tests and check coverage" — multi-step orchestration
17
+ p chat "write a rate limiter" — creates the file
18
+ p ask "what changed?" — backward-compatible alias
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import logging
24
+ from pathlib import Path
25
+
26
+ from rich.markdown import Markdown
27
+ from rich.panel import Panel
28
+ from rich.table import Table
29
+
30
+ from patchi.cli.console import con
31
+
32
+ MAX_HISTORY = 50
33
+ CHAT_HISTORY_FILE = ".patchi/memory/chat_history.json"
34
+
35
+
36
+ _log = logging.getLogger("patchi.cli.chat_cmd")
37
+
38
+
39
+ # ── History persistence ────────────────────────────────────────────────
40
+
41
+ def _load_chat_history(root: Path) -> list[dict]:
42
+ path = root / CHAT_HISTORY_FILE
43
+ if path.exists():
44
+ try:
45
+ return json.loads(path.read_text(encoding="utf-8"))
46
+ except Exception as e:
47
+ _log.warning("_load_chat_history failed: %s", e)
48
+ return []
49
+
50
+
51
+ def _save_chat_history(root: Path, history: list[dict]) -> None:
52
+ path = root / CHAT_HISTORY_FILE
53
+ path.parent.mkdir(parents=True, exist_ok=True)
54
+ path.write_text(json.dumps(history[-MAX_HISTORY:], indent=2), encoding="utf-8")
55
+
56
+
57
+ # ── System prompt ──────────────────────────────────────────────────────
58
+
59
+ BASE_PROMPT = """You are Patchi, the intelligent brain that controls the entire Patchi system.
60
+
61
+ You are NOT just a chat interface — you ARE the orchestrator with direct control over:
62
+ - Reading and writing files in the project
63
+ - Running any CLI command (scan, test, fix, etc.)
64
+ - Spawning specialized agents (security, testing, fixing, etc.)
65
+ - Accessing the full brain/memory state
66
+ - Making decisions based on context
67
+
68
+ When the user asks you to DO something, you should:
69
+ 1. Determine what tools/agents are needed
70
+ 2. Execute them using the brain's capabilities
71
+ 3. Report results with context
72
+
73
+ When the user asks about files, you can:
74
+ - Read the file contents directly
75
+ - Explain what the code does
76
+ - Suggest improvements
77
+ - Write changes if asked
78
+
79
+ You have full control. Use it wisely."""
80
+
81
+
82
+ # ── Context injection ──────────────────────────────────────────────────
83
+
84
+ def _build_injected_context(brain: dict, message: str, root=None) -> str:
85
+ """Build rich context from the brain using BrainContext for deeper understanding."""
86
+ m_lower = message.lower()
87
+ parts = []
88
+
89
+ # Try to load BrainContext for richer context
90
+ ctx = None
91
+ if root:
92
+ try:
93
+ from patchi.core.brain.brain_context import get_brain_context
94
+ ctx = get_brain_context(root)
95
+ except Exception as _exc:
96
+ _log.debug('suppressed: %s', _exc)
97
+
98
+ if ctx and ctx.is_loaded():
99
+ # Use BrainContext for rich context injection
100
+ parts.append(ctx.get_context_for_prompt(max_chars=2000))
101
+ else:
102
+ # Fallback to basic brain dict
103
+ file_count = brain.get("file_count", 0)
104
+ framework = brain.get("framework", "Unknown")
105
+ route_count = brain.get("route_count", 0)
106
+ health = brain.get("health_score", {})
107
+ health_total = health.get("total", 0) if isinstance(health, dict) else health
108
+ parts.append(f"Project: {file_count} files, {route_count} routes, framework: {framework}, health: {health_total}/100")
109
+
110
+ # Finding-specific context
111
+ if any(w in m_lower for w in ["finding", "issue", "vulnerability", "bug", "critical", "high", "error"]):
112
+ issues = brain.get("issues", []) or brain.get("findings", [])
113
+ if issues:
114
+ by_sev = {}
115
+ for f in issues:
116
+ if isinstance(f, dict):
117
+ sev = f.get("severity", "info")
118
+ by_sev[sev] = by_sev.get(sev, 0) + 1
119
+ if by_sev:
120
+ sev_str = ", ".join(f"{c} {s}" for s, c in sorted(by_sev.items()))
121
+ parts.append(f"Issues ({len(issues)} total): {sev_str}")
122
+ top = [f for f in issues if isinstance(f, dict) and f.get("severity") in ("critical", "high")][:5]
123
+ for f in top:
124
+ parts.append(f" [{f.get('severity', 'info')}] {f.get('file', '')}:{f.get('line', 0)} -- {f.get('message', '')[:80]}")
125
+
126
+ return "\n".join(parts) if parts else ""
127
+
128
+
129
+ # ── Force routing ──────────────────────────────────────────────────────
130
+
131
+ def _check_force_routing(message: str) -> tuple[str, str | None]:
132
+ """Check for forced routing prefixes. Returns (cleaned_message, force_mode)."""
133
+ msg = message.strip()
134
+ if msg.lower().startswith("reason:"):
135
+ return msg[7:].strip(), "reasoning"
136
+ if msg.lower().startswith("ai:"):
137
+ return msg[3:].strip(), "ai"
138
+ if msg.lower().startswith("tool:"):
139
+ return msg[5:].strip(), "tool"
140
+ return msg, None
141
+
142
+
143
+ # ── Explain All ────────────────────────────────────────────────────────────
144
+
145
+ def _show_explain_all() -> None:
146
+ """Show the full security knowledge base in a formatted table."""
147
+ from rich.table import Table
148
+
149
+ from patchi.cli.commands.explain_cmd import _EXPLANATIONS
150
+
151
+ con.print()
152
+ con.print("[bold #C8621A]Security Knowledge Base[/bold #C8621A]")
153
+ con.print("[dim]Complete reference for all security finding types.[/dim]")
154
+ con.print()
155
+
156
+ table = Table(show_header=True, header_style="bold #C8621A", box=None)
157
+ table.add_column("Type", style="cyan", min_width=25)
158
+ table.add_column("Title", style="white", min_width=25)
159
+ table.add_column("Severity", width=10)
160
+ table.add_column("CWE", width=12)
161
+ table.add_column("What", max_width=50)
162
+ table.add_column("How to Fix", max_width=40)
163
+
164
+ severity_colors = {
165
+ "critical": "red",
166
+ "high": "yellow",
167
+ "medium": "blue",
168
+ "low": "dim",
169
+ }
170
+
171
+ for ftype, info in sorted(_EXPLANATIONS.items()):
172
+ sev = info.get("severity", "?")
173
+ color = severity_colors.get(sev, "white")
174
+ table.add_row(
175
+ ftype,
176
+ info.get("title", "?"),
177
+ f"[{color}]{sev}[/{color}]",
178
+ info.get("cwe", "?"),
179
+ info.get("what", "?")[:50],
180
+ info.get("how", "?").split("\n")[0][:40],
181
+ )
182
+
183
+ con.print(table)
184
+ con.print()
185
+ con.print(f"[dim]{len(_EXPLANATIONS)} finding types in knowledge base.[/dim]")
186
+ con.print("[dim]Use `p chat 'explain sql injection'` for detailed info on any type.[/dim]")
187
+ con.print()
188
+
189
+
190
+ # ── Process message through the Brain ─────────────────────────────────
191
+
192
+ def _process_message(
193
+ message: str,
194
+ root: Path,
195
+ config: dict,
196
+ brain_state: dict,
197
+ json_output: bool = False,
198
+ history: list[dict] | None = None,
199
+ stream: bool = False,
200
+ ) -> str:
201
+ """Process a message using the Brain as the central intelligence."""
202
+ from patchi.core.ai.client import call_ai
203
+ from patchi.core.ai.orchestrator import Orchestrator
204
+
205
+ clean_msg, force_mode = _check_force_routing(message)
206
+
207
+ # Create brain instance with appropriate progress callback
208
+ if stream:
209
+ # Real-time streaming progress with rich formatting
210
+ _tool_count = [0]
211
+ _step_start = [0]
212
+
213
+ def on_progress(msg):
214
+ import time as _time
215
+ if "Executing" in msg:
216
+ _tool_count[0] += 1
217
+ _step_start[0] = _time.monotonic()
218
+ tool_name = msg.replace("Executing ", "").replace("...", "")
219
+ con.print(f" [bold cyan]⟳ Step {_tool_count[0]}:[/bold cyan] [bold]{tool_name}[/bold]")
220
+ elif "Spawning" in msg:
221
+ con.print(f" [bold magenta]🤖 {msg}[/bold magenta]")
222
+ elif "Planning" in msg or "Understanding" in msg:
223
+ con.print(f" [bold yellow]🧠 {msg}[/bold yellow]")
224
+ else:
225
+ con.print(f" [dim]{msg}[/dim]")
226
+
227
+ def on_event(payload: dict):
228
+ ev = payload.get("event", "")
229
+ data = payload.get("data", {})
230
+ if ev == "orchestrator.planned":
231
+ steps = data.get("steps", 0)
232
+ reasoning = data.get("reasoning", "")
233
+ con.print(f" [bold green]📋 Plan:[/bold green] {steps} steps")
234
+ if reasoning:
235
+ con.print(f" [dim]{reasoning[:100]}{'...' if len(reasoning) > 100 else ''}[/dim]")
236
+ elif ev == "orchestrator.completed":
237
+ success = data.get("success", False)
238
+ findings = data.get("findings", 0)
239
+ icon = "[green]✓[/green]" if success else "[red]✗[/red]"
240
+ con.print(f" {icon} [bold]Orchestration complete[/bold]")
241
+ if findings:
242
+ con.print(f" [dim]Findings: {findings}[/dim]")
243
+ elif ev == "security.finding":
244
+ sev = data.get("severity", "?").upper()
245
+ desc = data.get("description", data.get("type", ""))
246
+ con.print(f" [red][{sev}][/red] {desc[:80]}")
247
+ elif ev == "test.suite.completed":
248
+ passed = data.get("passed", 0)
249
+ failed = data.get("failed", 0)
250
+ con.print(f" [green]Tests: {passed} passed[/green], [red]{failed} failed[/red]")
251
+ elif ev == "agent.progress":
252
+ pct = data.get("progress_pct", 0)
253
+ current = data.get("current_file", "")
254
+ if current and current not in ["planning", "done"]:
255
+ con.print(f" [dim]→ {current} ({pct}%)[/dim]")
256
+ else:
257
+ def on_progress(msg):
258
+ con.print(f" [dim]{msg}[/dim]")
259
+ def on_event(payload: dict):
260
+ pass
261
+
262
+ orchestrator = Orchestrator(root, on_progress=on_progress, on_event=on_event)
263
+
264
+ # ── File operations (direct brain access) ──
265
+
266
+ # Check if user wants to read a file
267
+ read_patterns = ["read ", "show ", "cat ", "what's in ", "what is in ", "open ", "display "]
268
+ if any(clean_msg.lower().startswith(p) for p in read_patterns):
269
+ file_path = clean_msg
270
+ for p in read_patterns:
271
+ if file_path.lower().startswith(p):
272
+ file_path = file_path[len(p):].strip()
273
+ break
274
+
275
+ content = orchestrator.read_file(file_path)
276
+ if not content.startswith("ERROR:"):
277
+ con.print(f"\n[bold]📄 {file_path}[/bold]\n")
278
+ con.print(f"```python\n{content}\n```")
279
+ return f"[File: {file_path}]\n{content}"
280
+ else:
281
+ con.print(f"[yellow]{content}[/yellow]")
282
+ return content
283
+
284
+ # Check if user wants to write a file
285
+ write_patterns = ["write ", "create ", "save ", "make "]
286
+ if any(clean_msg.lower().startswith(p) for p in write_patterns):
287
+ # Extract file path and content
288
+ parts = clean_msg.split(":", 1) if ":" in clean_msg else clean_msg.split(" to ", 1)
289
+ if len(parts) == 2:
290
+ file_path = parts[0]
291
+ for p in write_patterns:
292
+ if file_path.lower().startswith(p):
293
+ file_path = file_path[len(p):].strip()
294
+ break
295
+ content = parts[1].strip()
296
+
297
+ result = orchestrator.write_file(file_path, content)
298
+ if result.get("success"):
299
+ con.print(f"\n[green]✓ Written {result['size']} bytes to {file_path}[/green]")
300
+ return f"[Written: {file_path}]"
301
+ else:
302
+ con.print(f"[red]Failed: {result.get('error', 'unknown')}[/red]")
303
+ return f"Failed: {result.get('error', 'unknown')}"
304
+
305
+ # ── Command execution ──
306
+ if clean_msg.lower().startswith("run ") or clean_msg.lower().startswith("exec "):
307
+ command = clean_msg
308
+ for p in ["run ", "exec "]:
309
+ if command.lower().startswith(p):
310
+ command = command[len(p):].strip()
311
+ break
312
+
313
+ result = orchestrator.run_command(command)
314
+ if result.get("success"):
315
+ con.print("\n[green]✓ Command succeeded[/green]")
316
+ if result.get("stdout"):
317
+ con.print(f"```\n{result['stdout']}\n```")
318
+ return result.get("stdout", "Command succeeded")
319
+ else:
320
+ con.print(f"[red]Command failed: {result.get('error', result.get('stderr', 'unknown'))}[/red]")
321
+ return f"Command failed: {result.get('error', result.get('stderr', 'unknown'))}"
322
+
323
+ # ── Agent spawning ──
324
+ spawn_patterns = ["spawn ", "run agent ", "use agent ", "activate "]
325
+ if any(clean_msg.lower().startswith(p) for p in spawn_patterns):
326
+ agent_type = clean_msg
327
+ for p in spawn_patterns:
328
+ if agent_type.lower().startswith(p):
329
+ agent_type = agent_type[len(p):].strip()
330
+ break
331
+
332
+ # Extract agent type from the message
333
+ for atype in ["security", "scanner", "proactive", "council", "smart"]:
334
+ if atype in agent_type.lower():
335
+ con.print(f"\n[bold cyan]🤖 Spawning {atype} agent...[/bold cyan]\n")
336
+ result = orchestrator.spawn_agent(atype, goal=clean_msg)
337
+ if result.get("success"):
338
+ con.print("[green]✓ Agent completed[/green]")
339
+ return json.dumps(result.get("result", {}), indent=2, default=str)[:2000]
340
+ else:
341
+ con.print(f"[red]Agent failed: {result.get('error', 'unknown')}[/red]")
342
+ return f"Agent failed: {result.get('error', 'unknown')}"
343
+
344
+ # ── LLM-powered orchestration (default) ──
345
+
346
+ # Build context
347
+ context = _build_injected_context(brain_state, clean_msg, root=root)
348
+ extra = f"\n\nRelevant context:\n{context}" if context else ""
349
+
350
+ # Build conversation history
351
+ history_text = ""
352
+ if history:
353
+ for msg in history[-10:]:
354
+ role = "Developer" if msg["role"] == "user" else "Brain"
355
+ history_text += f"{role}: {msg['content']}\n"
356
+
357
+ full_prompt = clean_msg
358
+ if history_text:
359
+ full_prompt = f"Previous conversation:\n{history_text}\nCurrent question: {clean_msg}"
360
+
361
+ # Get AI response
362
+ response = call_ai(config, BASE_PROMPT + extra, full_prompt)
363
+ if not response:
364
+ response = "I don't have an AI model configured. Set up with `p key add` or `p model set <model>`."
365
+
366
+ con.print()
367
+ con.print("[bold #4ADE80]Patchi:[/bold #4ADE80]")
368
+ con.print(Markdown(response))
369
+ con.print()
370
+
371
+ return response
372
+
373
+
374
+ # ── Help commands ──────────────────────────────────────────────────────
375
+
376
+ def _print_help():
377
+ """Print help information."""
378
+ con.print(Panel(
379
+ "[bold]Patchi Chat — The Brain[/bold]\n\n"
380
+ "[bold]Direct File Access:[/bold]\n"
381
+ " [cyan]read[/cyan] src/auth.py — read and explain a file\n"
382
+ " [cyan]write[/cyan] src/utils.py:code — create/overwrite a file\n\n"
383
+ "[bold]Command Execution:[/bold]\n"
384
+ " [cyan]run[/cyan] pytest tests/ — run any shell command\n"
385
+ " [cyan]exec[/cyan] ruff check src/ — execute a command\n\n"
386
+ "[bold]Agent Spawning:[/bold]\n"
387
+ " [cyan]spawn[/cyan] security — run security scan\n"
388
+ " [cyan]spawn[/cyan] council — multi-persona deliberation\n"
389
+ " [cyan]spawn[/cyan] smart — goal-driven smart agent\n\n"
390
+ "[bold]Natural Language:[/bold]\n"
391
+ " [cyan]scan[/cyan] this project — security scan\n"
392
+ " [cyan]fix[/cyan] critical findings — scan + fix pipeline\n"
393
+ " [cyan]test[/cyan] and check coverage — run tests\n"
394
+ " [cyan]what's in[/cyan] src/auth.py — read file\n\n"
395
+ "[bold]Commands:[/bold]\n"
396
+ " [green]tools[/green] — list available tools\n"
397
+ " [green]council[/green] — show council/persona info\n"
398
+ " [green]history[/green] — show chat history\n"
399
+ " [green]help[/green] — this help\n"
400
+ " [green]clear[/green] — clear chat history\n\n"
401
+ "[bold]Forced Routing:[/bold]\n"
402
+ " [yellow]reason:[/yellow] what changed? — force reasoning engine\n"
403
+ " [yellow]ai:[/yellow] explain this code — force AI chat\n"
404
+ " [yellow]tool:[/yellow] scan_vulnerabilities — force tool execution\n",
405
+ border_style="#C8621A",
406
+ ))
407
+
408
+
409
+ def _print_tools():
410
+ """Print available tools."""
411
+ try:
412
+ from patchi.core.ai.tools.registry import get_tool_registry
413
+ registry = get_tool_registry()
414
+
415
+ table = Table(title="Available Tools", box=None, show_lines=False)
416
+ table.add_column("Tool", style="cyan", min_width=20)
417
+ table.add_column("Description", style="white")
418
+ table.add_column("Category", style="yellow")
419
+
420
+ categories = {
421
+ "scan": ["analyze_project", "scan_vulnerabilities", "scan_project"],
422
+ "test": ["run_tests", "generate_tests", "browser_test", "screenshot", "visual_regression"],
423
+ "fix": ["generate_fix", "apply_patch", "verify_fix", "rollback_patch"],
424
+ "attack": ["attack_simulate", "red_team"],
425
+ "stress": ["stress_test"],
426
+ "knowledge": ["ask_brain", "explain_layer", "impact_analysis", "why_file_matters"],
427
+ "compliance": ["check_compliance"],
428
+ "code": ["write_file", "generate_code"],
429
+ }
430
+
431
+ tool_to_cat = {}
432
+ for cat, tools in categories.items():
433
+ for t in tools:
434
+ tool_to_cat[t] = cat
435
+
436
+ for tool in registry.list_tools():
437
+ table.add_row(
438
+ tool.name,
439
+ tool.description[:60] + "..." if len(tool.description) > 60 else tool.description,
440
+ tool_to_cat.get(tool.name, "other"),
441
+ )
442
+
443
+ con.print(table)
444
+ except Exception as e:
445
+ con.print(f"[red]Failed to load tools: {e}[/red]")
446
+
447
+
448
+ def _print_council_info(root: Path):
449
+ """Print council/persona information."""
450
+ try:
451
+ from patchi.core.brain.personas.base import list_personas
452
+
453
+ personas = list_personas()
454
+ con.print(f"\n[bold]🏛️ Council — {len(personas)} Personas[/bold]\n")
455
+
456
+ table = Table(box=None, show_lines=False)
457
+ table.add_column("Persona", style="cyan", min_width=20)
458
+ table.add_column("Style", style="yellow")
459
+ table.add_column("Focus", style="white")
460
+
461
+ style_names = {
462
+ "aggressive": "🔴 Aggressive",
463
+ "cautious": "🟡 Cautious",
464
+ "balanced": "🟢 Balanced",
465
+ "methodical": "🔵 Methodical",
466
+ "creative": "🟣 Creative",
467
+ }
468
+
469
+ for p in personas:
470
+ table.add_row(
471
+ p.name,
472
+ style_names.get(p.style.value if hasattr(p.style, 'value') else str(p.style), str(p.style)),
473
+ p.focus_domain,
474
+ )
475
+
476
+ con.print(table)
477
+ con.print("\n[dim]The Council deliberates on complex issues, combining multiple personas' perspectives.[/dim]")
478
+ con.print("[dim]Ask 'council' or mention 'council' in your message to trigger deliberation.[/dim]\n")
479
+ except Exception as e:
480
+ con.print(f"[red]Failed to load council info: {e}[/red]")
481
+
482
+
483
+ def _print_chat_history(history: list[dict]):
484
+ """Print chat history."""
485
+ if not history:
486
+ con.print("[dim]No chat history yet.[/dim]")
487
+ return
488
+
489
+ con.print(f"\n[bold]📋 Chat History ({len(history)} messages)[/bold]\n")
490
+ for msg in history[-20:]:
491
+ role = "You" if msg["role"] == "user" else "Brain"
492
+ content = msg["content"][:100] + "..." if len(msg["content"]) > 100 else msg["content"]
493
+ con.print(f" [bold]{role}:[/bold] {content}")
494
+ con.print()
495
+
496
+
497
+ # ── Main entry point ───────────────────────────────────────────────────
498
+
499
+ def run(
500
+ message: list[str] | str | None = None,
501
+ json_output: bool = False,
502
+ root: Path | None = None,
503
+ stream: bool = False,
504
+ explain_all: bool = False,
505
+ ) -> None:
506
+ """Entry point for `p chat` and `p ask` (backward compatible).
507
+
508
+ Args:
509
+ message: Single message or list of words.
510
+ json_output: Output as JSON.
511
+ root: Override project root.
512
+ stream: Show real-time tool execution progress.
513
+ explain_all: Show full security knowledge base.
514
+ """
515
+ try:
516
+ from patchi.core.config import require_project_root
517
+ r = root or require_project_root()
518
+ except RuntimeError as e:
519
+ con.print(f"[red]{e}[/red]")
520
+ return
521
+
522
+ from patchi.core import config as cfg
523
+ from patchi.core import memory as mem
524
+
525
+ config = cfg.load(r)
526
+ brain_state = mem.get_brain(r)
527
+
528
+ # --explain-all: show full security knowledge base
529
+ if explain_all:
530
+ _show_explain_all()
531
+ return
532
+
533
+ # Single message mode
534
+ if message:
535
+ if isinstance(message, list):
536
+ message = " ".join(message)
537
+ if not message.strip():
538
+ con.print("[red]Error: no message provided[/red]")
539
+ return
540
+
541
+ _process_message(message, r, config, brain_state, json_output=json_output, stream=stream)
542
+ return
543
+
544
+ # Interactive mode
545
+ con.print()
546
+ stream_label = " [green](streaming)[/green]" if stream else ""
547
+ con.print(
548
+ Panel(
549
+ f"[bold]Patchi Chat — The Brain[/bold]{stream_label}\n"
550
+ "[dim]Direct access to files, commands, agents, and intelligence.[/dim]\n"
551
+ "[dim]Type 'quit' to exit, 'help' for commands, 'tools' to list available tools.[/dim]",
552
+ border_style="#C8621A",
553
+ )
554
+ )
555
+ con.print()
556
+
557
+ history = _load_chat_history(r)
558
+
559
+ while True:
560
+ try:
561
+ user_input = con.input("[bold #C8621A]You:[/bold #C8621A] ").strip()
562
+ except (EOFError, KeyboardInterrupt):
563
+ _save_chat_history(r, history)
564
+ con.print("\n[dim]Chat ended.[/dim]")
565
+ return
566
+
567
+ if not user_input:
568
+ continue
569
+ if user_input.lower() in ("quit", "exit", "q"):
570
+ _save_chat_history(r, history)
571
+ con.print("[dim]Chat ended.[/dim]")
572
+ return
573
+ if user_input.lower() == "help":
574
+ _print_help()
575
+ continue
576
+ if user_input.lower() == "tools":
577
+ _print_tools()
578
+ continue
579
+ if user_input.lower() == "council":
580
+ _print_council_info(r)
581
+ continue
582
+ if user_input.lower() == "history":
583
+ _print_chat_history(history)
584
+ continue
585
+ if user_input.lower().startswith("clear"):
586
+ history.clear()
587
+ _save_chat_history(r, history)
588
+ con.print("[dim]Chat history cleared.[/dim]")
589
+ continue
590
+
591
+ # Process the message through the Brain
592
+ response = _process_message(user_input, r, config, brain_state, history=history, stream=stream)
593
+ if response:
594
+ history.append({"role": "user", "content": user_input})
595
+ history.append({"role": "assistant", "content": response})
596
+ history[:] = history[-MAX_HISTORY:]
597
+ _save_chat_history(r, history)