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,140 @@
1
+ """
2
+ `p learn` — Learning Brain (Phase 5).
3
+
4
+ Shows what Patchi has learned from your accept/reject patterns and the reusable
5
+ fix patterns it has captured, lets you re-derive conventions from a fresh scan,
6
+ and lets you reset that learning.
7
+
8
+ p learn — show learned accept/reject patterns + which fix types are skipped
9
+ p learn patterns — show captured fix patterns
10
+ p learn --force — re-derive conventions/patterns from the current project
11
+ p learn --reset — forget all learned patterns for this project
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import logging
17
+ from pathlib import Path
18
+
19
+ from rich.table import Table
20
+
21
+ from patchi.cli.console import con
22
+ from patchi.core.brain import learning
23
+ from patchi.core.config import require_project_root
24
+
25
+ _log = logging.getLogger("patchi.cli.learn_cmd")
26
+
27
+
28
+ def run(
29
+ force: bool = False,
30
+ sub: str | None = None,
31
+ reset: bool = False,
32
+ root: Path | None = None,
33
+ ) -> None:
34
+ try:
35
+ r = root or require_project_root()
36
+ except RuntimeError as e:
37
+ con.print(f"[red]{e}[/red]")
38
+ return
39
+
40
+ if reset:
41
+ path = r / ".patchi" / "learning.json"
42
+ if path.exists():
43
+ try:
44
+ path.unlink()
45
+ con.print("[#4ADE80]✓[/#4ADE80] Learning data reset for this project.")
46
+ except Exception as e:
47
+ _log.warning("run failed: %s", e)
48
+ con.print("[red]Could not reset learning data.[/red]")
49
+ else:
50
+ con.print("[dim]Nothing to reset.[/dim]")
51
+ con.print()
52
+ return
53
+
54
+ if sub == "patterns":
55
+ _show_patterns(r)
56
+ return
57
+
58
+ if force:
59
+ _relearn(r)
60
+
61
+ summary = learning.get_summary(r)
62
+ has_data = summary["acceptances"] or summary["rejections"] or summary.get("agent_trust")
63
+ if not has_data:
64
+ con.print()
65
+ con.print(
66
+ "[dim]No learning data yet. Patchi learns from the fixes you accept or reject "
67
+ "(e.g. [bold]p auto <files> --reject <type>[/bold]).[/dim]"
68
+ )
69
+ con.print()
70
+ return
71
+
72
+ table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
73
+ table.add_column("Fix type", width=20)
74
+ table.add_column("Accepted", width=10)
75
+ table.add_column("Rejected", width=10)
76
+ table.add_column("Status", width=14)
77
+ types = list(summary["acceptances"].keys()) + list(summary["rejections"].keys())
78
+ for t in sorted(set(types)):
79
+ acc = summary["acceptances"].get(t, 0)
80
+ rej = summary["rejections"].get(t, 0)
81
+ status = "[yellow]skipped[/yellow]" if t in summary["skip_types"] else "[dim]active[/dim]"
82
+ table.add_row(t, str(acc), str(rej), status)
83
+
84
+ con.print()
85
+ con.print(table)
86
+ if summary.get("agent_trust"):
87
+ con.print()
88
+ con.print("[dim]Agent trust:[/dim]")
89
+ for agent, score in summary["agent_trust"].items():
90
+ con.print(f" {agent}: {score}")
91
+ con.print()
92
+ con.print(
93
+ "[dim]Run [bold]p learn patterns[/bold] to see captured fix strategies, "
94
+ "[bold]p learn --reset[/bold] to forget.[/dim]"
95
+ )
96
+ con.print()
97
+
98
+
99
+ def _show_patterns(r: Path) -> None:
100
+ patterns = learning.list_fix_patterns(r)
101
+ if not patterns:
102
+ con.print()
103
+ con.print("[dim]No fix patterns captured yet.[/dim]")
104
+ con.print()
105
+ return
106
+ table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
107
+ table.add_column("Type", width=18)
108
+ table.add_column("File", width=30)
109
+ table.add_column("Strategy", width=22)
110
+ table.add_column("Summary", width=34)
111
+ for p in patterns:
112
+ table.add_row(
113
+ p.get("finding_type", ""),
114
+ p.get("file_pattern", ""),
115
+ p.get("fix_strategy", ""),
116
+ p.get("fix_summary", ""),
117
+ )
118
+ con.print()
119
+ con.print(table)
120
+ con.print()
121
+
122
+
123
+ def _relearn(r: Path) -> None:
124
+ """Re-derive conventions/patterns from a fresh proactive scan of the project."""
125
+ try:
126
+ from patchi.core.brain.proactive import ProactiveAgent
127
+ from patchi.core.brain.scanner import FileScanner
128
+
129
+ scanner = FileScanner(r)
130
+ file_infos = scanner.scan()
131
+ agent = ProactiveAgent(r)
132
+ captured = 0
133
+ for fi in file_infos:
134
+ fixes = agent.analyze_change([fi.path], file_infos, None, None, include_format=False)
135
+ for f in fixes:
136
+ learning.record_fix_pattern(f.fix_type, f.file, "auto", f.description, r)
137
+ captured += 1
138
+ con.print(f"[dim]Re-learned {captured} fix pattern(s) from the current project.[/dim]")
139
+ except Exception as e:
140
+ con.print(f"[dim]Re-learn skipped: {e}[/dim]")
@@ -0,0 +1,104 @@
1
+ """
2
+ p link — hybrid linking for non-monorepo (auto discovery tag + manual).
3
+
4
+ p link status — show linking config + suggestion
5
+ p link add --frontend ./ --backend ../backend --frontend-url http://localhost:3000 --backend-url http://localhost:5000
6
+ p link confirm — confirm auto suggestion
7
+ p link remove — remove linking
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from pathlib import Path
14
+
15
+ from rich.panel import Panel
16
+
17
+ from patchi.cli.console import con
18
+ from patchi.core.config import require_project_root
19
+
20
+ _CFG = ".patchi/config.json"
21
+ _SUG = ".patchi/linking_suggestion.json"
22
+
23
+
24
+ def _load_cfg(root: Path) -> dict:
25
+ p = root / _CFG
26
+ if p.exists():
27
+ try:
28
+ return json.loads(p.read_text(encoding="utf-8"))
29
+ except Exception:
30
+ return {}
31
+ return {}
32
+
33
+
34
+ def _save_cfg(root: Path, cfg: dict) -> None:
35
+ p = root / _CFG
36
+ p.parent.mkdir(parents=True, exist_ok=True)
37
+ tmp = p.with_suffix(".tmp")
38
+ tmp.write_text(json.dumps(cfg, indent=2), encoding="utf-8")
39
+ tmp.replace(p)
40
+
41
+
42
+ def run(action: str | None = None, root: Path | None = None, **kwargs) -> None:
43
+ try:
44
+ r = root or require_project_root()
45
+ except RuntimeError as e:
46
+ con.print(f"[red]{e}[/red]")
47
+ return
48
+
49
+ if action in (None, "status"):
50
+ cfg = _load_cfg(r)
51
+ link = cfg.get("linking")
52
+ sug_path = r / _SUG
53
+ if link:
54
+ con.print(Panel(f"[bold]Linking:[/bold] {link}", border_style="#4ADE80"))
55
+ elif sug_path.exists():
56
+ try:
57
+ sug = json.loads(sug_path.read_text(encoding="utf-8"))
58
+ con.print(Panel(f"[yellow]Suggestion:[/yellow] frontend {sug.get('frontend')} ↔ backend {sug.get('backend')}\nRun [bold]p link confirm[/bold] to activate", border_style="#FF8C42"))
59
+ except Exception:
60
+ con.print("[dim]No linking suggestion[/dim]")
61
+ else:
62
+ con.print("[dim]No linking configured — monorepo auto-detected or run p link add[/dim]")
63
+ return
64
+
65
+ if action == "add":
66
+
67
+ # Called via registry: kwargs has frontend, backend, frontend_url, backend_url
68
+ front = kwargs.get("frontend") or "./"
69
+ back = kwargs.get("backend")
70
+ furl = kwargs.get("frontend_url") or "http://localhost:3000"
71
+ burl = kwargs.get("backend_url") or "http://localhost:5000"
72
+ if not back:
73
+ con.print("[red]--backend required (e.g. ../backend)[/red]")
74
+ return
75
+ cfg = _load_cfg(r)
76
+ cfg["linking"] = {"mode": "separate", "frontend": front, "backend": back, "frontend_url": furl, "backend_url": burl, "confirmed": True}
77
+ _save_cfg(r, cfg)
78
+ con.print(f"[green]Linking added:[/green] {front} ↔ {back}")
79
+ return
80
+
81
+ if action == "confirm":
82
+ sug_path = r / _SUG
83
+ if not sug_path.exists():
84
+ con.print("[yellow]No suggestion to confirm — run p scan first or p link add[/yellow]")
85
+ return
86
+ try:
87
+ sug = json.loads(sug_path.read_text(encoding="utf-8"))
88
+ cfg = _load_cfg(r)
89
+ cfg["linking"] = {**sug, "mode": "separate", "confirmed": True}
90
+ _save_cfg(r, cfg)
91
+ sug_path.unlink(missing_ok=True)
92
+ con.print(f"[green]Link confirmed:[/green] {sug}")
93
+ except Exception as e:
94
+ con.print(f"[red]Confirm failed: {e}[/red]")
95
+ return
96
+
97
+ if action == "remove":
98
+ cfg = _load_cfg(r)
99
+ cfg.pop("linking", None)
100
+ _save_cfg(r, cfg)
101
+ con.print("[dim]Linking removed[/dim]")
102
+ return
103
+
104
+ con.print(f"[yellow]Unknown link action {action!r}[/yellow]")
@@ -0,0 +1,57 @@
1
+ """
2
+ `p log` — Git changelog.
3
+
4
+ Usage:
5
+ p log [--since HEAD~N] — show recent commits with changed files
6
+ p log --since HEAD~5 — show last 5 commits
7
+ p log --since 2026-01-01 — show commits since a date
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+
14
+ from rich.table import Table
15
+
16
+ from patchi.cli.console import con
17
+ from patchi.core.brain.git_aware import changelog
18
+ from patchi.core.config import require_project_root
19
+
20
+
21
+ def run(since: str = "HEAD~10", root: Path | None = None) -> None:
22
+ try:
23
+ r = root or require_project_root()
24
+ except RuntimeError as e:
25
+ con.print(f"[red]{e}[/red]")
26
+ return
27
+
28
+ entries = changelog(r, since)
29
+
30
+ if not entries:
31
+ con.print("[dim]No git history found or not a git repository.[/dim]")
32
+ return
33
+
34
+ table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
35
+ table.add_column("Hash", style="bold #F2EDD6", width=10)
36
+ table.add_column("Date", width=12)
37
+ table.add_column("Author", width=18)
38
+ table.add_column("Message", style="dim", width=50)
39
+ table.add_column("Files", style="dim", width=20)
40
+
41
+ for entry in entries:
42
+ files_str = ", ".join(entry.get("files", [])[:3])
43
+ if len(entry.get("files", [])) > 3:
44
+ files_str += f" +{len(entry['files']) - 3} more"
45
+ table.add_row(
46
+ entry.get("hash", "?"),
47
+ entry.get("date", ""),
48
+ entry.get("author", "")[:18],
49
+ entry.get("summary", "")[:50],
50
+ files_str,
51
+ )
52
+
53
+ con.print()
54
+ con.print(table)
55
+ con.print()
56
+ con.print(f"[dim]Total: {len(entries)} entries (since {since})[/dim]")
57
+ con.print()
@@ -0,0 +1,215 @@
1
+ from pathlib import Path
2
+
3
+ from rich.panel import Panel
4
+ from rich.prompt import Confirm, Prompt
5
+ from rich.table import Table
6
+ from rich.text import Text
7
+
8
+ from patchi.cli.console import con
9
+ from patchi.core import memory as mem
10
+ from patchi.core.config import require_project_root
11
+ from patchi.core.constants import MemoryCategory
12
+
13
+ """
14
+ `p memory` — view and manage Patchi's memory.
15
+
16
+ Subcommands:
17
+ p memory — show all categories with counts
18
+ p memory show <category> — show full data for a category
19
+ p memory delete <category>— clear a category (with warning)
20
+ p memory delete all — full reset (requires CONFIRM)
21
+ """
22
+
23
+
24
+ def run_show_all(root: Path | None = None) -> None:
25
+ """p memory — show all categories."""
26
+ try:
27
+ r = root or require_project_root()
28
+ except RuntimeError as e:
29
+ con.print(f"[red]{e}[/red]")
30
+ return
31
+
32
+ data = mem.summary(r)
33
+
34
+ table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
35
+ table.add_column("Category", style="bold #F2EDD6", width=16)
36
+ table.add_column("Items", style="#B8A898")
37
+ table.add_column("Description", style="dim")
38
+
39
+ descriptions = {
40
+ "brain": "Brain's understanding of your app",
41
+ "patches": "Applied fix history with diffs",
42
+ "failed": "Patches that failed and were rolled back",
43
+ "scans": "Last results from every scanner agent",
44
+ "issues": "Known unfixed issues",
45
+ "restrictions": "No-touch zones and scan-only paths",
46
+ "tokens": "Dev access token names (values never stored)",
47
+ "layers": "Layered brain (Pillar 1) — module/subsytem/project map",
48
+ "charter": "Project guard-rail charter (Pillar 2)",
49
+ }
50
+
51
+ for category, info in data.items():
52
+ if "count" in info:
53
+ count_str = str(info["count"])
54
+ elif "keys" in info:
55
+ count_str = f"{len(info['keys'])} sections"
56
+ else:
57
+ count_str = "—"
58
+
59
+ table.add_row(category, count_str, descriptions.get(category, ""))
60
+
61
+ con.print()
62
+ con.print(
63
+ Panel(table, title="[bold #C8621A]Patchi Memory[/bold #C8621A]", border_style="#2A3D28")
64
+ )
65
+ con.print()
66
+ con.print(
67
+ "[dim]Commands: [bold]p memory show <category>[/bold] · [bold]p memory delete <category>[/bold][/dim]"
68
+ )
69
+ con.print()
70
+
71
+
72
+ def run_show(category_str: str, root: Path | None = None) -> None:
73
+ """p memory show <category>"""
74
+ import json
75
+
76
+ try:
77
+ r = root or require_project_root()
78
+ except RuntimeError as e:
79
+ con.print(f"[red]{e}[/red]")
80
+ return
81
+
82
+ # Special-case: 'scans' gets a formatted view instead of raw JSON
83
+ if category_str.lower() in ("scans", "scan"):
84
+ _show_scans(r)
85
+ return
86
+
87
+ try:
88
+ category = _parse_category(category_str)
89
+ except ValueError as e:
90
+ con.print(f"[red]{e}[/red]")
91
+ return
92
+
93
+ data = mem.read(category, r)
94
+ con.print()
95
+ con.print(f"[bold #F2EDD6]Memory: {category.value}[/bold #F2EDD6]")
96
+ con.print()
97
+ con.print_json(json.dumps(data, indent=2, default=str))
98
+ con.print()
99
+
100
+
101
+ def _show_scans(root: Path) -> None:
102
+ """Structured view of scan results — one row per agent."""
103
+ from rich.table import Table
104
+
105
+ scans = mem.get_scan_results(root)
106
+ if not scans:
107
+ con.print("[dim]No scan results yet. Run [bold]p scan[/bold] first.[/dim]")
108
+ return
109
+
110
+ con.print()
111
+ con.print("[bold #F2EDD6]Last Scan Results by Agent[/bold #F2EDD6]")
112
+ con.print()
113
+
114
+ table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
115
+ table.add_column("Agent", style="bold #F2EDD6", width=26)
116
+ table.add_column("Status", width=10)
117
+ table.add_column("Findings", justify="right", width=10)
118
+ table.add_column("Files", justify="right", width=8)
119
+ table.add_column("Duration", justify="right", width=10)
120
+
121
+ colors = {"done": "#4ADE80", "failed": "#FF4D6D", "skipped": "dim"}
122
+ for agent_name, data in sorted(scans.items()):
123
+ status = data.get("status", "?")
124
+ table.add_row(
125
+ agent_name,
126
+ Text(status, style=colors.get(status, "white")),
127
+ str(data.get("finding_count", 0)),
128
+ str(data.get("files_scanned", 0)),
129
+ f"{data.get('duration_ms', 0)}ms",
130
+ )
131
+
132
+ con.print(table)
133
+ con.print()
134
+
135
+
136
+ def run_delete(category_str: str, root: Path | None = None) -> None:
137
+ """p memory delete <category> — with warning"""
138
+ try:
139
+ r = root or require_project_root()
140
+ except RuntimeError as e:
141
+ con.print(f"[red]{e}[/red]")
142
+ return
143
+
144
+ if category_str.lower() == "all":
145
+ _delete_all(r)
146
+ return
147
+
148
+ try:
149
+ category = _parse_category(category_str)
150
+ except ValueError as e:
151
+ con.print(f"[red]{e}[/red]")
152
+ return
153
+
154
+ consequence = _delete_consequence(category)
155
+ con.print()
156
+ con.print(f"[yellow]Warning:[/yellow] {consequence}")
157
+ con.print()
158
+
159
+ confirmed = Confirm.ask(f"Delete [bold]{category.value}[/bold] memory?", default=False)
160
+ if confirmed:
161
+ mem.delete(category, r)
162
+ con.print(f"[#4ADE80]✓[/#4ADE80] [bold]{category.value}[/bold] memory cleared.")
163
+ else:
164
+ con.print("[dim]Cancelled.[/dim]")
165
+ con.print()
166
+
167
+
168
+ def _delete_all(root: Path) -> None:
169
+ """Full memory reset — requires typing CONFIRM."""
170
+ con.print()
171
+ con.print("[red bold]Full memory reset.[/red bold]\n")
172
+ con.print()
173
+ typed = Prompt.ask("[bold]Type CONFIRM to proceed[/bold]")
174
+ if typed.strip() == "CONFIRM":
175
+ mem.delete_all(root)
176
+ con.print("[#4ADE80]✓[/#4ADE80] All memory cleared. Patchi starts fresh on the next run.")
177
+ else:
178
+ con.print("[dim]Cancelled.[/dim]")
179
+ con.print()
180
+
181
+
182
+ # ── Helpers ────────────────────────────────────────────────────────────────────
183
+
184
+
185
+ def _parse_category(s: str) -> MemoryCategory:
186
+ valid = {c.value: c for c in MemoryCategory}
187
+ # Allow shorthand aliases
188
+ aliases = {
189
+ "patch": MemoryCategory.PATCHES,
190
+ "fail": MemoryCategory.FAILED,
191
+ "scan": MemoryCategory.SCANS,
192
+ "issue": MemoryCategory.ISSUES,
193
+ "restrict": MemoryCategory.RESTRICTIONS,
194
+ "token": MemoryCategory.TOKENS,
195
+ }
196
+ cleaned = s.lower().strip()
197
+ if cleaned in valid:
198
+ return valid[cleaned]
199
+ if cleaned in aliases:
200
+ return aliases[cleaned]
201
+ raise ValueError(f"Unknown category: {s!r}\nValid: {', '.join(valid.keys())}")
202
+
203
+
204
+ def _delete_consequence(category: MemoryCategory) -> str:
205
+ consequences = {
206
+ MemoryCategory.BRAIN: "Deleting Brain state means Patchi will re-learn your entire app before the next action.",
207
+ MemoryCategory.PATCHES: "Deleting patch history means you can no longer undo or review past fixes.",
208
+ MemoryCategory.FAILED: "Deleting failed patch history clears all recorded failure reasons.",
209
+ MemoryCategory.SCANS: "Deleting scan results means Patchi will re-scan before the next action.",
210
+ MemoryCategory.ISSUES: "Deleting known issues clears all recorded unfixed findings.",
211
+ MemoryCategory.RESTRICTIONS: "Deleting restrictions removes all no-touch and scan-only zones. "
212
+ "Patchi will have access to all files on next run.",
213
+ MemoryCategory.TOKENS: "Deleting dev tokens removes all stored access token references.",
214
+ }
215
+ return consequences.get(category, "This area will be cleared and rescanned on next run.")