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,330 @@
1
+ """Findings route — findings list with filters + chain/intent tabs, DAST screenshots, history."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json as _json
6
+ import logging
7
+ import os
8
+ import sqlite3
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+
12
+ from fastapi import APIRouter, Request
13
+ from fastapi.responses import HTMLResponse, JSONResponse
14
+ from fastapi.templating import Jinja2Templates
15
+
16
+ _log = logging.getLogger("patchi.web.routes.findings")
17
+
18
+
19
+ router = APIRouter()
20
+ templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates"))
21
+
22
+
23
+ def _load_chain_intent(root: Path) -> dict:
24
+ """Load persisted chain/intent data from the last scan."""
25
+ ci_path = root / ".patchi" / "chain_intent.json"
26
+ if ci_path.is_file():
27
+ try:
28
+ return _json.loads(ci_path.read_text(encoding="utf-8"))
29
+ except Exception as _exc:
30
+ _log.warning('_load_chain_intent failed: %s', _exc)
31
+ return {"chains": [], "intent_report": None}
32
+
33
+
34
+ def _load_visual_regression(root: Path) -> dict:
35
+ """Load visual regression screenshots, diffs, and baselines."""
36
+ evidence_dir = root / ".patchi" / "evidence" / "screenshots" / "visual_regression"
37
+ baseline_dir = root / ".patchi" / "visual_baselines"
38
+
39
+ screenshots = []
40
+ if evidence_dir.is_dir():
41
+ for f in sorted(
42
+ evidence_dir.glob("*.png"), key=lambda x: x.stat().st_mtime, reverse=True
43
+ ):
44
+ try:
45
+ stat = f.stat()
46
+ is_diff = "_diff" in f.stem
47
+ screenshots.append(
48
+ {
49
+ "name": f.stem,
50
+ "filename": f.name,
51
+ "path": str(f.relative_to(root)),
52
+ "serve_url": f"/api/live-testing/screenshot/{f.name}",
53
+ "size_kb": round(stat.st_size / 1024, 1),
54
+ "is_diff": is_diff,
55
+ # Pair diff images with their source: "foo_diff" -> "foo"
56
+ "source_name": f.stem.replace("_diff", "") if is_diff else None,
57
+ }
58
+ )
59
+ except Exception as _exc:
60
+ _log.warning('_load_visual_regression failed: %s', _exc)
61
+
62
+ # Pair screenshots: for each diff, find its source screenshot
63
+ diff_map = {s["source_name"]: s for s in screenshots if s["is_diff"]}
64
+ pairs = []
65
+ seen = set()
66
+ for s in screenshots:
67
+ if s["is_diff"]:
68
+ continue # handled via source pairing
69
+ pair = {"source": s, "diff": diff_map.get(s["name"])}
70
+ pairs.append(pair)
71
+ seen.add(s["name"])
72
+ # Add orphaned diffs (source missing)
73
+ for name, diff in diff_map.items():
74
+ if name not in seen:
75
+ pairs.append({"source": None, "diff": diff})
76
+
77
+ baseline_count = 0
78
+ if baseline_dir.is_dir():
79
+ baseline_count = len(list(baseline_dir.rglob("*.png")))
80
+
81
+ return {
82
+ "screenshots": screenshots,
83
+ "pairs": pairs,
84
+ "total": len([s for s in screenshots if not s["is_diff"]]),
85
+ "diffs": len([s for s in screenshots if s["is_diff"]]),
86
+ "baselines": baseline_count,
87
+ "total_size_kb": round(sum(s["size_kb"] for s in screenshots), 1),
88
+ }
89
+
90
+
91
+ def _load_dast_evidence(root: Path) -> dict[str, dict]:
92
+ """Load DAST screenshot evidence from .patchi/evidence/dast/."""
93
+ evidence_dir = root / ".patchi" / "evidence" / "dast"
94
+ evidence_map: dict[str, dict] = {}
95
+ if not evidence_dir.is_dir():
96
+ return evidence_map
97
+ for entry in os.scandir(evidence_dir):
98
+ if not entry.is_file():
99
+ continue
100
+ name = entry.name
101
+ stem = Path(name).stem # e.g. "dast_xss_reflected_abc123"
102
+ # Strip the random suffix (last _XXXXX) to get the test name
103
+ parts = stem.rsplit("_", 1)
104
+ test_name = parts[0] if len(parts) > 1 else stem
105
+ # Extract category from test name: "dast_xss_reflected" -> "xss"
106
+ cat = ""
107
+ if test_name.startswith("dast_"):
108
+ cat = test_name[5:].split("_")[0]
109
+ evidence_map.setdefault(cat, {}).setdefault(test_name, []).append(
110
+ f"/evidence/dast/{name}"
111
+ )
112
+ return evidence_map
113
+
114
+
115
+ @router.get("/findings", response_class=HTMLResponse)
116
+ async def findings(request: Request):
117
+ root = request.app.state.root
118
+ from patchi.core import memory as mem
119
+
120
+ mem.get_brain(root)
121
+ scan_results = mem.get_scan_results(root)
122
+
123
+ # Flatten findings from all agents and attach remediation
124
+ all_findings = []
125
+ try:
126
+ from patchi.core.security.remediation import get_remediation, get_remediation_confidence
127
+
128
+ has_remediation = True
129
+ except ImportError:
130
+ has_remediation = False
131
+
132
+ # Collect DAST-specific metadata and scan stats
133
+ dast_findings_count = 0
134
+ dast_tests_run = 0
135
+ dast_target = ""
136
+ scan_agent_count = len(scan_results)
137
+
138
+ for agent_name, data in scan_results.items():
139
+ # Accumulate DAST metadata from the DASTAgent result
140
+ if agent_name == "DASTAgent":
141
+ dast_findings_count = data.get("findings_count", 0)
142
+ dast_tests_run = data.get("tests_run", 0)
143
+ dast_target = data.get("target_url", "")
144
+
145
+ for f in data.get("findings", []):
146
+ f["agent"] = agent_name
147
+ # Flag DAST findings for template
148
+ if agent_name == "DASTAgent" or f.get("type", "").startswith("dast_"):
149
+ f["is_dast"] = True
150
+ # Attach remediation suggestion for non-chain findings
151
+ if has_remediation:
152
+ ftype = f.get("type", "")
153
+ rem = get_remediation(ftype)
154
+ if rem:
155
+ f["remediation"] = {
156
+ "action": rem.action,
157
+ "code_pattern": rem.code_pattern,
158
+ "auto_fixable": rem.auto_fixable,
159
+ "playbook_id": rem.playbook_id,
160
+ "confidence": round(get_remediation_confidence(ftype, root), 2),
161
+ }
162
+ all_findings.append(f)
163
+
164
+ # Sort by severity
165
+ sev_order = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4}
166
+ all_findings.sort(key=lambda f: (0 if f.get("is_dast") else 1, sev_order.get(f.get("severity", "info"), 5)))
167
+
168
+ # Load DAST screenshot evidence
169
+ dast_evidence = _load_dast_evidence(root)
170
+
171
+ # Attach screenshot paths to DAST findings
172
+ for f in all_findings:
173
+ if f.get("is_dast"):
174
+ ftype = f.get("type", "")
175
+ cat = ftype.replace("dast_", "") if ftype.startswith("dast_") else ""
176
+ if cat in dast_evidence:
177
+ # Get first screenshot for this category
178
+ screenshots = []
179
+ for _test_name, paths in dast_evidence[cat].items():
180
+ screenshots.extend(paths[:1]) # one per test type
181
+ f["screenshots"] = screenshots[:3] # max 3 per finding
182
+ else:
183
+ f["screenshots"] = []
184
+
185
+ # Load chain/intent data with remediation suggestions
186
+ ci = _load_chain_intent(root)
187
+ chains = ci.get("chains", [])
188
+ intent = ci.get("intent_report")
189
+
190
+ # Attach remediation to each chain step
191
+ try:
192
+ from patchi.core.security.remediation import get_remediation_for_step
193
+
194
+ for chain in chains:
195
+ chain["remediations"] = [
196
+ get_remediation_for_step(step) for step in chain.get("steps", [])
197
+ ]
198
+ except ImportError:
199
+ pass
200
+
201
+ # Build chain-membership map: (file, line, type) -> list of chain indices
202
+ chain_members: dict[str, list[int]] = {}
203
+ for ci, chain in enumerate(chains):
204
+ for step in chain.get("steps", []):
205
+ key = f"{step.get('file', '')}:{step.get('line', 0)}:{step.get('type', '')}"
206
+ chain_members.setdefault(key, []).append(ci)
207
+
208
+ # Load charter violations
209
+ charter_violations: list[dict] = []
210
+ try:
211
+ from patchi.core.security.charter import check_all_violations, load_charter
212
+ charter = load_charter(root)
213
+ if charter.rules:
214
+ charter_violations = [v.to_dict() for v in check_all_violations(charter)]
215
+ except Exception as _exc:
216
+ _log.warning('findings failed: %s', _exc)
217
+
218
+ # Load scan history
219
+ scan_history = _load_scan_history(root)
220
+
221
+ # Load visual regression data
222
+ vr_data = _load_visual_regression(root)
223
+
224
+ return templates.TemplateResponse(
225
+ request,
226
+ "findings.html",
227
+ {
228
+ "request": request,
229
+ "findings": all_findings,
230
+ "total": len(all_findings),
231
+ "chains": chains,
232
+ "intent": intent,
233
+ "chain_members": chain_members,
234
+ "charter_violations": charter_violations,
235
+ "dast_findings_count": dast_findings_count,
236
+ "dast_tests_run": dast_tests_run,
237
+ "dast_target": dast_target,
238
+ "scan_agent_count": scan_agent_count,
239
+ "scan_history": scan_history,
240
+ "vr_pairs": vr_data["pairs"],
241
+ "vr_total": vr_data["total"],
242
+ "vr_diffs": vr_data["diffs"],
243
+ "vr_baselines": vr_data["baselines"],
244
+ "vr_size_kb": vr_data["total_size_kb"],
245
+ },
246
+ )
247
+
248
+
249
+ @router.get("/api/chains")
250
+ async def api_chains(request: Request):
251
+ """JSON endpoint for chain/intent data."""
252
+ root = request.app.state.root
253
+ return JSONResponse(_load_chain_intent(root))
254
+
255
+
256
+ @router.get("/api/findings/dast-evidence")
257
+ async def api_dast_evidence(request: Request):
258
+ """JSON endpoint for DAST screenshot evidence."""
259
+ root = request.app.state.root
260
+ return JSONResponse(_load_dast_evidence(root))
261
+
262
+
263
+ def _load_scan_history(root: Path, limit: int = 50) -> list[dict]:
264
+ """Load scan history from the SQLite database."""
265
+ db_path = root / ".patchi" / "patchi_history.db"
266
+ if not db_path.is_file():
267
+ return []
268
+ try:
269
+ db = sqlite3.connect(str(db_path))
270
+ cursor = db.cursor()
271
+ cursor.execute(
272
+ "SELECT scan_id, timestamp, tool, findings_count, "
273
+ "severity_breakdown, duration_ms, health_score, metrics "
274
+ "FROM scan_history ORDER BY timestamp DESC LIMIT ?",
275
+ (limit,),
276
+ )
277
+ rows = cursor.fetchall()
278
+ db.close()
279
+ results = []
280
+ for row in rows:
281
+ entry = {
282
+ "scan_id": row[0],
283
+ "timestamp": row[1],
284
+ "tool": row[2],
285
+ "findings_count": row[3],
286
+ "severity_breakdown": _json.loads(row[4]) if row[4] else {},
287
+ "duration_ms": row[5],
288
+ "health_score": row[6],
289
+ "metrics": _json.loads(row[7]) if row[7] else {},
290
+ }
291
+ results.append(entry)
292
+ return results
293
+ except Exception:
294
+ return []
295
+
296
+
297
+ @router.get("/api/findings/history")
298
+ async def api_findings_history(request: Request, limit: int = 50):
299
+ """JSON endpoint for scan history."""
300
+ root = request.app.state.root
301
+ return JSONResponse(_load_scan_history(root, limit))
302
+
303
+
304
+ @router.get("/api/findings/history/export")
305
+ async def api_findings_history_export(request: Request):
306
+ """Export full scan history + current findings as JSON for CI/CD."""
307
+ root = request.app.state.root
308
+ history = _load_scan_history(root, limit=500)
309
+
310
+ # Also include current findings snapshot
311
+ from patchi.core import memory as mem
312
+ scan_results = mem.get_scan_results(root)
313
+ current_findings = []
314
+ for agent_name, data in scan_results.items():
315
+ for f in data.get("findings", []):
316
+ f["agent"] = agent_name
317
+ current_findings.append(f)
318
+
319
+ export = {
320
+ "exported_at": datetime.utcnow().isoformat() + "Z",
321
+ "project": str(root),
322
+ "history": history,
323
+ "current_findings": current_findings,
324
+ "summary": {
325
+ "total_scans": len(history),
326
+ "total_findings": len(current_findings),
327
+ "latest_health_score": history[0]["health_score"] if history else None,
328
+ },
329
+ }
330
+ return JSONResponse(export)
@@ -0,0 +1,35 @@
1
+ """Guard route — hosted guard monitoring."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from fastapi import APIRouter, Request
8
+ from fastapi.responses import HTMLResponse
9
+ from fastapi.templating import Jinja2Templates
10
+
11
+ router = APIRouter()
12
+ templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates"))
13
+
14
+
15
+ @router.get("/guard", response_class=HTMLResponse)
16
+ async def guard(request: Request):
17
+ root = request.app.state.root
18
+ from patchi.core import config as cfg
19
+
20
+ try:
21
+ config = cfg.load(root)
22
+ except Exception:
23
+ config = {}
24
+
25
+ hosted = config.get("hosted", {})
26
+
27
+ return templates.TemplateResponse(
28
+ request,
29
+ "guard.html",
30
+ {
31
+ "request": request,
32
+ "enabled": hosted.get("enabled", False),
33
+ "log_path": hosted.get("log_path", ""),
34
+ },
35
+ )
@@ -0,0 +1,126 @@
1
+ """History route — scan and fix history with filtering/sorting."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from pathlib import Path
8
+
9
+ from fastapi import APIRouter, Query, Request
10
+ from fastapi.responses import HTMLResponse, JSONResponse
11
+ from fastapi.templating import Jinja2Templates
12
+
13
+ logger = logging.getLogger("patchi.web.history")
14
+
15
+ router = APIRouter()
16
+ templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates"))
17
+
18
+
19
+ @router.get("/history", response_class=HTMLResponse)
20
+ async def history_page(request: Request):
21
+ root = request.app.state.root
22
+ from patchi.core import memory as mem
23
+
24
+ brain = mem.get_brain(root)
25
+ patches = mem.list_patches(root)
26
+
27
+ scan_history = []
28
+ try:
29
+ from patchi.core.security.history import patchi_get_history
30
+
31
+ scan_history = patchi_get_history(root, limit=100)
32
+ except Exception as e:
33
+ logger.warning("Failed to fetch scan history: %s", e)
34
+
35
+ # Extract unique tools for filter dropdown
36
+ tools = sorted({s.get("tool", "unknown") for s in scan_history})
37
+
38
+ return templates.TemplateResponse(
39
+ request,
40
+ "history.html",
41
+ {
42
+ "request": request,
43
+ "patches": patches,
44
+ "scan_history": scan_history,
45
+ "scan_history_json": json.dumps(scan_history),
46
+ "tools": tools,
47
+ "file_count": brain.get("file_count", 0),
48
+ "route_count": brain.get("route_count", 0),
49
+ "framework": brain.get("framework", "Unknown"),
50
+ },
51
+ )
52
+
53
+
54
+ @router.get("/api/history/filter")
55
+ async def filter_history(
56
+ request: Request,
57
+ date_from: str | None = Query(None, description="Start date (YYYY-MM-DD)"),
58
+ date_to: str | None = Query(None, description="End date (YYYY-MM-DD)"),
59
+ severity: str | None = Query(None, description="Comma-separated severities"),
60
+ tool: str | None = Query(None, description="Filter by tool name"),
61
+ sort_by: str = Query("timestamp", description="Sort field"),
62
+ sort_dir: str = Query("desc", description="Sort direction (asc/desc)"),
63
+ limit: int = Query(50, description="Max results"),
64
+ ):
65
+ """Filter and sort scan history."""
66
+ root = request.app.state.root
67
+
68
+ try:
69
+ from patchi.core.security.history import patchi_get_history
70
+
71
+ all_history = patchi_get_history(root, limit=200)
72
+ except Exception as e:
73
+ logger.warning("Failed to fetch scan history: %s", e)
74
+ all_history = []
75
+
76
+ # Apply filters
77
+ filtered = all_history
78
+
79
+ # Date range filter
80
+ if date_from:
81
+ filtered = [s for s in filtered if s.get("timestamp", "") >= date_from]
82
+ if date_to:
83
+ # Include the full end day
84
+ filtered = [s for s in filtered if s.get("timestamp", "") <= date_to + "T23:59:59"]
85
+
86
+ # Severity filter (check if any matching severity has count > 0)
87
+ if severity:
88
+ sev_list = [s.strip() for s in severity.split(",") if s.strip()]
89
+ if sev_list:
90
+ filtered = [
91
+ s for s in filtered
92
+ if any(
93
+ s.get("severity_breakdown", {}).get(sev, 0) > 0
94
+ for sev in sev_list
95
+ )
96
+ ]
97
+
98
+ # Tool filter
99
+ if tool and tool != "all":
100
+ filtered = [s for s in filtered if s.get("tool", "") == tool]
101
+
102
+ # Sorting
103
+ reverse = sort_dir == "desc"
104
+ if sort_by == "timestamp":
105
+ filtered.sort(key=lambda s: s.get("timestamp", ""), reverse=reverse)
106
+ elif sort_by == "findings":
107
+ filtered.sort(key=lambda s: s.get("findings_count", 0), reverse=reverse)
108
+ elif sort_by == "health":
109
+ filtered.sort(key=lambda s: s.get("health_score", 0), reverse=reverse)
110
+ elif sort_by == "duration":
111
+ filtered.sort(key=lambda s: s.get("duration_ms", 0), reverse=reverse)
112
+ elif sort_by == "severity":
113
+ # Sort by total critical+high findings
114
+ def _sev_score(s):
115
+ sb = s.get("severity_breakdown", {})
116
+ return sb.get("critical", 0) * 100 + sb.get("high", 0) * 10
117
+ filtered.sort(key=_sev_score, reverse=reverse)
118
+
119
+ # Limit
120
+ filtered = filtered[:limit]
121
+
122
+ return JSONResponse({
123
+ "total": len(all_history),
124
+ "filtered": len(filtered),
125
+ "results": filtered,
126
+ })
@@ -0,0 +1,52 @@
1
+ """Public marketing landing — Snyk × Linear style, standalone.
2
+
3
+ Not the localhost app dashboard (/). This is the external-facing
4
+ story: hero + 3-up + Brain + Council + Live Tests + CLI parity + freemium.
5
+ Shares tokens with the app but is a separate band layout for marketing.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from pathlib import Path
12
+
13
+ from fastapi import APIRouter, Request
14
+ from fastapi.responses import HTMLResponse
15
+ from starlette.templating import Jinja2Templates
16
+
17
+ router = APIRouter()
18
+
19
+ templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates"))
20
+ templates.env.filters["tojson"] = lambda v: json.dumps(v)
21
+
22
+
23
+ @router.get("/landing", response_class=HTMLResponse)
24
+ @router.get("/welcome", response_class=HTMLResponse)
25
+ async def landing(request: Request):
26
+ root = request.app.state.root
27
+ from patchi.core import memory as mem
28
+ from patchi.core.health import compute as compute_health
29
+
30
+ brain = mem.get_brain(root)
31
+ hs = compute_health(root)
32
+
33
+ # Stats for hero proof + metrics
34
+ scan_results = mem.get_scan_results(root)
35
+ total_findings = sum(len(v.get("findings", [])) for v in scan_results.values() if isinstance(v, dict))
36
+
37
+ return templates.TemplateResponse(
38
+ request,
39
+ "landing.html",
40
+ {
41
+ "request": request,
42
+ "health_score": hs.total,
43
+ "health_grade": hs.grade,
44
+ "file_count": brain.get("file_count", 0),
45
+ "route_count": brain.get("route_count", 0),
46
+ "framework": brain.get("framework", "Unknown"),
47
+ "languages": brain.get("languages", {}),
48
+ "recent_findings": total_findings,
49
+ "active_domains": brain.get("active_security_domains", [])[:6],
50
+ "project_purpose": brain.get("project_purpose", "AI-powered code security & quality agent colony"),
51
+ },
52
+ )
@@ -0,0 +1,24 @@
1
+ """Live Testing page — browser automation, screenshots, stress tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from fastapi import APIRouter, Request
8
+ from starlette.templating import Jinja2Templates
9
+
10
+ router = APIRouter()
11
+ _templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates"))
12
+
13
+
14
+ @router.get("/live-tests")
15
+ async def live_tests_page(request: Request):
16
+ """Render the live testing dashboard."""
17
+ return _templates.TemplateResponse("live_testing.html", {"request": request})
18
+
19
+
20
+ @router.get("/live-testing")
21
+ async def live_testing_redirect():
22
+ """Legacy path — forwards to the unified live tests page."""
23
+ from fastapi.responses import RedirectResponse
24
+ return RedirectResponse(url="/live-tests", status_code=307)
@@ -0,0 +1,57 @@
1
+ """Review route — patch review with diffs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from fastapi import APIRouter, Request
8
+ from fastapi.responses import HTMLResponse
9
+ from fastapi.templating import Jinja2Templates
10
+
11
+ router = APIRouter()
12
+ templates = Jinja2Templates(directory=str(Path(__file__).parent.parent / "templates"))
13
+
14
+
15
+ @router.get("/review", response_class=HTMLResponse)
16
+ async def review(request: Request):
17
+ root = request.app.state.root
18
+ from patchi.core import memory as mem
19
+
20
+ raw_patches = mem.read("patches", root)
21
+
22
+ # Enrich each patch with diff content if available
23
+ patches = []
24
+ for p in raw_patches:
25
+ if isinstance(p, dict):
26
+ # Build a unified diff string from FileChange objects if not already present
27
+ if not p.get("diff") and p.get("changes"):
28
+ diff_lines = []
29
+ for change in p["changes"]:
30
+ path = change.get("path", "unknown")
31
+ before = change.get("before", "")
32
+ proposed = change.get("proposed", "")
33
+ if before or proposed:
34
+ import difflib
35
+
36
+ diff = list(
37
+ difflib.unified_diff(
38
+ before.splitlines(keepends=True) if before else [],
39
+ proposed.splitlines(keepends=True) if proposed else [],
40
+ fromfile=f"a/{path}",
41
+ tofile=f"b/{path}",
42
+ lineterm="",
43
+ )
44
+ )
45
+ diff_lines.extend(diff)
46
+ p["diff"] = "\n".join(diff_lines) if diff_lines else ""
47
+ patches.append(p)
48
+
49
+ return templates.TemplateResponse(
50
+ request,
51
+ "review.html",
52
+ {
53
+ "request": request,
54
+ "patches": patches,
55
+ "total": len(patches),
56
+ },
57
+ )