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,416 @@
1
+ """
2
+ `p report` — Generate a structured analysis report for the current project.
3
+
4
+ Usage:
5
+ p report — Print full report to terminal
6
+ p report export — Write report to .patchi/report.md
7
+ p report export --format json — Write JSON report to .patchi/report.json
8
+
9
+ Report sections:
10
+ 1. Project summary (name, framework, file count, last scan)
11
+ 2. Health score breakdown (A–F grade + component scores)
12
+ 3. Open issues (by severity)
13
+ 4. Security findings
14
+ 5. Test results (last run)
15
+ 6. Recent patches applied
16
+ 7. Recommendations (top 5 actionable items)
17
+
18
+ All data comes from brain memory — no re-scan is triggered.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import time
25
+ from datetime import UTC, datetime
26
+ from pathlib import Path
27
+
28
+ from rich.panel import Panel
29
+ from rich.rule import Rule
30
+
31
+ from patchi.cli.console import con
32
+ from patchi.core.config import require_project_root
33
+
34
+ # ── Entry point ────────────────────────────────────────────────────────────────
35
+
36
+
37
+ def run(
38
+ report_cmd: str | None = None,
39
+ export: bool = False,
40
+ fmt: str = "markdown",
41
+ weekly: bool = False,
42
+ root: Path | None = None,
43
+ ) -> None:
44
+ """Entry point for `p report [export|weekly]`."""
45
+ if report_cmd == "export":
46
+ export = True
47
+ elif report_cmd == "weekly":
48
+ weekly = True
49
+ try:
50
+ r = root or require_project_root()
51
+ except RuntimeError as e:
52
+ con.print(f"[red]{e}[/red]")
53
+ return
54
+
55
+ from patchi.core import health
56
+ from patchi.core import memory as mem
57
+
58
+ if weekly:
59
+ _send_weekly(r)
60
+ return
61
+
62
+ brain = mem.get_brain(r)
63
+ scans = mem.get_scan_results(r)
64
+ patches = mem.list_patches(r)
65
+ score = health.compute(r)
66
+
67
+ report_data = _build_report(r, brain, scans, patches, score)
68
+
69
+ if export:
70
+ _export_report(r, report_data, fmt)
71
+ else:
72
+ _print_report(report_data, score)
73
+
74
+
75
+ # ── Report builder ─────────────────────────────────────────────────────────────
76
+
77
+
78
+ def _build_report(
79
+ root: Path,
80
+ brain: dict,
81
+ scans: dict,
82
+ patches: list,
83
+ score,
84
+ ) -> dict:
85
+ """Build the structured report dict from in-memory data."""
86
+ now = datetime.now(UTC).isoformat(timespec="seconds") + "Z"
87
+
88
+ framework = brain.get("framework", "Unknown")
89
+ file_count = brain.get("file_count", 0)
90
+ route_count = brain.get("route_count", 0)
91
+ last_scan = brain.get("last_scan", "Never")
92
+
93
+ # Aggregate all findings from recent scans
94
+ findings_by_severity: dict[str, list[dict]] = {
95
+ "critical": [],
96
+ "high": [],
97
+ "medium": [],
98
+ "low": [],
99
+ }
100
+ for agent_name, scan_data in scans.items():
101
+ for finding in scan_data.get("findings", []):
102
+ sev = finding.get("severity", "low").lower()
103
+ if sev in findings_by_severity:
104
+ findings_by_severity[sev].append(
105
+ {
106
+ **finding,
107
+ "agent": agent_name,
108
+ }
109
+ )
110
+
111
+ # Top recommendations
112
+ recommendations = _build_recommendations(score, findings_by_severity, brain, scans)
113
+
114
+ # Recent patches (last 10)
115
+ recent_patches = [
116
+ {
117
+ "id": p.get("id", "?"),
118
+ "file": p.get("file", "?"),
119
+ "status": p.get("status", "?"),
120
+ "created": p.get("created_at", ""),
121
+ }
122
+ for p in patches[-10:]
123
+ ]
124
+
125
+ return {
126
+ "generated_at": now,
127
+ "project": {
128
+ "root": str(root),
129
+ "framework": framework,
130
+ "file_count": file_count,
131
+ "route_count": route_count,
132
+ "last_scan": last_scan,
133
+ },
134
+ "health": score.to_dict(),
135
+ "findings": findings_by_severity,
136
+ "finding_totals": {k: len(v) for k, v in findings_by_severity.items()},
137
+ "patches": recent_patches,
138
+ "recommendations": recommendations,
139
+ }
140
+
141
+
142
+ def _build_recommendations(
143
+ score, findings_by_severity: dict, brain: dict, scans: dict
144
+ ) -> list[str]:
145
+ recs: list[str] = []
146
+
147
+ # Security
148
+ if findings_by_severity["critical"]:
149
+ recs.append(
150
+ f"Fix {len(findings_by_severity['critical'])} CRITICAL security findings immediately — "
151
+ "these are active vulnerabilities."
152
+ )
153
+ if findings_by_severity["high"]:
154
+ recs.append(
155
+ f"Address {len(findings_by_severity['high'])} HIGH severity findings before next release."
156
+ )
157
+
158
+ # Test coverage
159
+ if score.test_coverage < 50:
160
+ recs.append(
161
+ "Test coverage is below 50%. Add unit tests for business-critical paths "
162
+ "— run `p test unit` to see gaps."
163
+ )
164
+
165
+ # Dead code
166
+ if score.dead_code < 60:
167
+ recs.append(
168
+ "Dead code ratio is high. Run `p scan` then review `p fix` suggestions "
169
+ "to remove unused modules."
170
+ )
171
+
172
+ # Dependency health
173
+ if score.dependency < 70:
174
+ recs.append(
175
+ "Vulnerable dependencies detected. Run `p security deps` for details "
176
+ "and upgrade affected packages."
177
+ )
178
+
179
+ # Circular deps
180
+ circular = len(brain.get("circular_deps", []))
181
+ if circular > 0:
182
+ recs.append(
183
+ f"{circular} circular dependency cycle(s) detected. These make the codebase "
184
+ "brittle — refactor to break the cycle."
185
+ )
186
+
187
+ # All green
188
+ if not recs:
189
+ recs.append("Project health looks good. Keep running `p scan` on every PR.")
190
+
191
+ return recs[:5]
192
+
193
+
194
+ # ── Terminal renderer ──────────────────────────────────────────────────────────
195
+
196
+
197
+ def _print_report(data: dict, score) -> None:
198
+ con.print()
199
+ con.print(Rule("[bold #C8621A]Patchi Analysis Report[/bold #C8621A]", style="#C8621A"))
200
+ con.print(f" [dim]Generated: {data['generated_at']}[/dim]")
201
+ con.print()
202
+
203
+ # Project summary
204
+ proj = data["project"]
205
+ con.print(f" [bold]Project[/bold] {proj['root']}")
206
+ con.print(f" [bold]Framework[/bold] {proj['framework']}")
207
+ con.print(
208
+ f" [bold]Files[/bold] {proj['file_count']} | [bold]Routes[/bold] {proj['route_count']}"
209
+ )
210
+ con.print(f" [bold]Last scan[/bold] {proj['last_scan']}")
211
+ con.print()
212
+
213
+ # Health score
214
+ h = data["health"]
215
+ color = score.color
216
+ con.print(
217
+ Panel(
218
+ f"[bold {color}]Score: {h['total']}/100 Grade: {h['grade']}[/bold {color}]\n"
219
+ f"[dim]"
220
+ f"Security {h['components']['security']:.0f} "
221
+ f"Tests {h['components']['test_coverage']:.0f} "
222
+ f"Dead-code {h['components']['dead_code']:.0f} "
223
+ f"Deps {h['components']['dependency']:.0f} "
224
+ f"Contract {h['components']['contract']:.0f}"
225
+ f"[/dim]",
226
+ title="Health Score",
227
+ border_style=color,
228
+ padding=(0, 1),
229
+ )
230
+ )
231
+ con.print()
232
+
233
+ # Findings
234
+ totals = data["finding_totals"]
235
+ if any(totals.values()):
236
+ con.print(" [bold #F2EDD6]Findings[/bold #F2EDD6]")
237
+ sev_colors = {
238
+ "critical": "#FF4D6D",
239
+ "high": "#FF8C42",
240
+ "medium": "#FACC15",
241
+ "low": "#4ADE80",
242
+ }
243
+ for sev in ("critical", "high", "medium", "low"):
244
+ count = totals[sev]
245
+ if count:
246
+ color = sev_colors[sev]
247
+ con.print(f" [{color}]●[/{color}] {sev.upper():<10} {count}")
248
+ for finding in data["findings"][sev][:3]:
249
+ msg = finding.get("message", "")[:70]
250
+ con.print(f" [dim]→ {msg}[/dim]")
251
+ con.print()
252
+
253
+ # Patches
254
+ if data["patches"]:
255
+ con.print(
256
+ f" [bold #F2EDD6]Recent Patches[/bold #F2EDD6] [dim](last {len(data['patches'])})[/dim]"
257
+ )
258
+ for p in data["patches"][-5:]:
259
+ status_color = "#4ADE80" if p["status"] == "applied" else "#6B7280"
260
+ con.print(
261
+ f" [{status_color}]·[/{status_color}] "
262
+ f"[dim]{p['id'][:8]}[/dim] {p['file'][:50]} [dim]{p['status']}[/dim]"
263
+ )
264
+ con.print()
265
+
266
+ # Recommendations
267
+ con.print(" [bold #F2EDD6]Recommendations[/bold #F2EDD6]")
268
+ for i, rec in enumerate(data["recommendations"], 1):
269
+ con.print(f" [dim]{i}.[/dim] {rec}")
270
+ con.print()
271
+ con.print(Rule(style="dim"))
272
+ con.print(" [dim]Run `p report export` to save this as a Markdown file.[/dim]")
273
+ con.print()
274
+
275
+
276
+ # ── Export ────────────────────────────────────────────────────────────────────
277
+
278
+
279
+ def _export_report(root: Path, data: dict, fmt: str) -> None:
280
+ report_dir = root / ".patchi"
281
+ report_dir.mkdir(parents=True, exist_ok=True)
282
+
283
+ if fmt == "json":
284
+ out_path = report_dir / "report.json"
285
+ out_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
286
+ elif fmt == "sarif":
287
+ from patchi.core import ci_bundle
288
+
289
+ findings = []
290
+ for sev_list in data.get("findings", {}).values():
291
+ findings.extend(sev_list)
292
+ out_path = report_dir / "report.sarif.json"
293
+ out_path.write_text(ci_bundle.render_findings(findings, "sarif"), encoding="utf-8")
294
+ else:
295
+ out_path = report_dir / "report.md"
296
+ out_path.write_text(_render_markdown(data), encoding="utf-8")
297
+
298
+ con.print()
299
+ con.print(f"[#4ADE80]✓[/#4ADE80] Report saved → [bold]{out_path}[/bold]")
300
+ con.print()
301
+
302
+
303
+ def _render_markdown(data: dict) -> str:
304
+ lines: list[str] = []
305
+ h = data["health"]
306
+ proj = data["project"]
307
+
308
+ lines.append("# Patchi Analysis Report")
309
+ lines.append("")
310
+ lines.append(f"**Generated:** {data['generated_at']} ")
311
+ lines.append(f"**Project:** `{proj['root']}` ")
312
+ lines.append(f"**Framework:** {proj['framework']} ")
313
+ lines.append(f"**Files:** {proj['file_count']} | **Routes:** {proj['route_count']} ")
314
+ lines.append(f"**Last scan:** {proj['last_scan']} ")
315
+ lines.append("")
316
+ lines.append("---")
317
+ lines.append("")
318
+ lines.append(f"## Health Score: {h['total']}/100 (Grade {h['grade']})")
319
+ lines.append("")
320
+ lines.append("| Component | Score |")
321
+ lines.append("|-----------|-------|")
322
+ for comp, val in h["components"].items():
323
+ lines.append(f"| {comp.replace('_', ' ').title()} | {val:.0f} |")
324
+ lines.append("")
325
+
326
+ totals = data["finding_totals"]
327
+ if any(totals.values()):
328
+ lines.append("## Findings")
329
+ lines.append("")
330
+ sev_map = {"critical": "🔴", "high": "🟠", "medium": "🟡", "low": "🟢"}
331
+ for sev in ("critical", "high", "medium", "low"):
332
+ count = totals[sev]
333
+ if count:
334
+ lines.append(f"### {sev_map[sev]} {sev.title()} ({count})")
335
+ lines.append("")
336
+ for finding in data["findings"][sev][:10]:
337
+ msg = finding.get("message", "")
338
+ file = finding.get("file", "")
339
+ lines.append(f"- `{file}` — {msg}")
340
+ lines.append("")
341
+
342
+ if data["patches"]:
343
+ lines.append("## Recent Patches")
344
+ lines.append("")
345
+ lines.append("| ID | File | Status |")
346
+ lines.append("|----|------|--------|")
347
+ for p in data["patches"]:
348
+ lines.append(f"| `{p['id'][:8]}` | `{p['file']}` | {p['status']} |")
349
+ lines.append("")
350
+
351
+ lines.append("## Recommendations")
352
+ lines.append("")
353
+ for i, rec in enumerate(data["recommendations"], 1):
354
+ lines.append(f"{i}. {rec}")
355
+ lines.append("")
356
+ lines.append("---")
357
+ lines.append("*Generated by Patchi — https://patchi.dev*")
358
+ lines.append("")
359
+
360
+ return "\n".join(lines)
361
+
362
+
363
+ def _send_weekly(root: Path) -> None:
364
+ """Generate and send weekly health report (MISS-09)."""
365
+ from datetime import datetime
366
+
367
+ from patchi.core import config as cfg
368
+ from patchi.core import health
369
+ from patchi.core import memory as mem
370
+
371
+ brain = mem.get_brain(root)
372
+ scans = mem.get_scan_results(root)
373
+ patches = mem.list_patches(root)
374
+ score = health.compute(root)
375
+ config = cfg.load(root)
376
+
377
+ report_data = _build_report(root, brain, scans, patches, score)
378
+ markdown = _render_markdown(report_data)
379
+ out_path = (
380
+ root / ".patchi" / f"weekly_report_{datetime.now(UTC).strftime('%Y%m%d')}.md"
381
+ )
382
+ out_path.write_text(markdown, encoding="utf-8")
383
+
384
+ con.print()
385
+ con.print(f"[#4ADE80]✓[/#4ADE80] Weekly report saved → [bold]{out_path}[/bold]")
386
+
387
+ # Send via email if a notification channel is configured
388
+ notifications = config.get("notifications", [])
389
+ email_channels = [
390
+ n for n in notifications if n.get("type") == "email" or n.get("channel_type") == "email"
391
+ ]
392
+ if email_channels:
393
+ try:
394
+ from patchi.core.notifications.digest import DigestQueue
395
+
396
+ digest = DigestQueue(root)
397
+ digest._queue = [
398
+ {
399
+ "event": "weekly_report",
400
+ "title": f"Patchi Weekly Report — {datetime.now(UTC).strftime('%Y-%m-%d')}",
401
+ "body": markdown[:10000],
402
+ "severity": "info",
403
+ "timestamp": time.time(),
404
+ }
405
+ ]
406
+ digest.flush(channels=email_channels)
407
+ con.print(
408
+ f"[#4ADE80]✓[/#4ADE80] Report sent to [bold]{len(email_channels)}[/bold] email channel(s)"
409
+ )
410
+ except Exception as e:
411
+ con.print(f"[yellow]Could not send via email: {e}[/yellow]")
412
+ else:
413
+ con.print(
414
+ "[dim]No email channel configured. Set up notifications in config to auto-send.[/dim]"
415
+ )
416
+ con.print()
@@ -0,0 +1,127 @@
1
+ from pathlib import Path
2
+
3
+ from rich.table import Table
4
+ from rich.text import Text
5
+
6
+ from patchi.cli.console import con
7
+ from patchi.core import config as cfg
8
+ from patchi.core.constants import RestrictionType
9
+
10
+ """
11
+ `p restrict` — manage no-touch zones, scan-only paths, and sensitive files.
12
+
13
+ Subcommands:
14
+ p restrict add <path> — add as no-touch zone
15
+ p restrict scan-only <path> — add as scan-only zone
16
+ p restrict sensitive <path> — mark as sensitive file
17
+ p restrict list — list all constraints
18
+ p restrict remove <path> — remove a constraint
19
+ p restrict disable <path> — temporarily disable
20
+ p restrict enable <path> — re-enable
21
+ """
22
+
23
+
24
+ _TYPE_COLORS = {
25
+ RestrictionType.NO_TOUCH.value: "red",
26
+ RestrictionType.SCAN_ONLY.value: "yellow",
27
+ RestrictionType.SENSITIVE.value: "#C8621A",
28
+ }
29
+
30
+ _TYPE_LABELS = {
31
+ RestrictionType.NO_TOUCH.value: "no-touch",
32
+ RestrictionType.SCAN_ONLY.value: "scan-only",
33
+ RestrictionType.SENSITIVE.value: "sensitive",
34
+ }
35
+
36
+
37
+ def run_add(path: str, rtype: RestrictionType, reason: str = "", root: Path | None = None) -> None:
38
+ try:
39
+ cfg.add_restriction(path, rtype, reason, root)
40
+ label = _TYPE_LABELS[rtype.value]
41
+ con.print(f"[#4ADE80]✓[/#4ADE80] Added [bold]{path}[/bold] as [bold]{label}[/bold].")
42
+ _show_type_note(rtype)
43
+ except RuntimeError as e:
44
+ con.print(f"[red]{e}[/red]")
45
+
46
+
47
+ def run_list(root: Path | None = None) -> None:
48
+ try:
49
+ restrictions = cfg.get_restrictions(root)
50
+ except RuntimeError as e:
51
+ con.print(f"[red]{e}[/red]")
52
+ return
53
+
54
+ con.print()
55
+ if not restrictions:
56
+ con.print("[dim]No restrictions configured. All files are accessible.[/dim]")
57
+ con.print()
58
+ return
59
+
60
+ table = Table(show_header=True, header_style="bold #C8621A", box=None, pad_edge=False)
61
+ table.add_column("Path", style="bold #F2EDD6")
62
+ table.add_column("Type", width=12)
63
+ table.add_column("Status", width=10)
64
+ table.add_column("Reason", style="dim")
65
+
66
+ for r in restrictions:
67
+ rtype = r.get("type", "no_touch")
68
+ color = _TYPE_COLORS.get(rtype, "white")
69
+ label = _TYPE_LABELS.get(rtype, rtype)
70
+ status = (
71
+ Text("active", style="#4ADE80")
72
+ if r.get("enabled", True)
73
+ else Text("disabled", style="dim")
74
+ )
75
+
76
+ table.add_row(
77
+ r["path"],
78
+ Text(label, style=color),
79
+ status,
80
+ r.get("reason", ""),
81
+ )
82
+
83
+ con.print(table)
84
+ con.print()
85
+
86
+
87
+ def run_remove(path: str, root: Path | None = None) -> None:
88
+ try:
89
+ removed = cfg.remove_restriction(path, root)
90
+ if removed:
91
+ con.print(f"[#4ADE80]✓[/#4ADE80] Removed restriction for [bold]{path}[/bold].")
92
+ con.print("[dim]Patchi will rescan this area on the next run.[/dim]")
93
+ else:
94
+ con.print(f"[yellow]No restriction found for {path!r}.[/yellow]")
95
+ except RuntimeError as e:
96
+ con.print(f"[red]{e}[/red]")
97
+
98
+
99
+ def run_disable(path: str, root: Path | None = None) -> None:
100
+ try:
101
+ ok = cfg.toggle_restriction(path, enabled=False, root=root)
102
+ if ok:
103
+ con.print(f"[dim]Restriction for [bold]{path}[/bold] disabled (not removed).[/dim]")
104
+ else:
105
+ con.print(f"[yellow]No restriction found for {path!r}.[/yellow]")
106
+ except RuntimeError as e:
107
+ con.print(f"[red]{e}[/red]")
108
+
109
+
110
+ def run_enable(path: str, root: Path | None = None) -> None:
111
+ try:
112
+ ok = cfg.toggle_restriction(path, enabled=True, root=root)
113
+ if ok:
114
+ con.print(f"[#4ADE80]✓[/#4ADE80] Restriction for [bold]{path}[/bold] re-enabled.")
115
+ else:
116
+ con.print(f"[yellow]No restriction found for {path!r}.[/yellow]")
117
+ except RuntimeError as e:
118
+ con.print(f"[red]{e}[/red]")
119
+
120
+
121
+ def _show_type_note(rtype: RestrictionType) -> None:
122
+ notes = {
123
+ RestrictionType.NO_TOUCH: "Patchi will never read, scan, or modify this path.",
124
+ RestrictionType.SCAN_ONLY: "Patchi will scan and report but never propose fixes here.",
125
+ RestrictionType.SENSITIVE: "Patchi acknowledges this file exists but never reads its contents.",
126
+ }
127
+ con.print(f"[dim]{notes[rtype]}[/dim]")