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,620 @@
1
+ """
2
+ Proactive Auto-Fix — Fix charter violations on file save/commit.
3
+
4
+ Automatically fixes SAFE issues (imports, formatting, headers) while
5
+ leaving DANGEROUS issues (secrets, eval, imports) for manual review.
6
+
7
+ Rule types and auto-fix mapping:
8
+ boundary → flag only (requires human review)
9
+ convention → auto-fix if safe (formatting, headers, naming)
10
+ security → flag only (requires human review)
11
+ stack → flag only (requires human review)
12
+
13
+ Safe auto-fixes:
14
+ - headers_required: Add missing file header
15
+ - snake_case / camel_case: Rename variables
16
+ - lines: Flag only (splitting requires judgment)
17
+ - docstrings_required: Add empty docstring placeholder
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import logging
23
+ from dataclasses import dataclass, field
24
+ from pathlib import Path
25
+
26
+ from patchi.core.security.charter import (
27
+ CharterRule,
28
+ RuleType,
29
+ load_charter,
30
+ )
31
+
32
+ _log = logging.getLogger("patchi.security.auto_fix_proactive")
33
+
34
+ # ── Auto-Fix Strategy Mapping ────────────────────────────────────────────────
35
+
36
+ # Convention rules that are safe to auto-fix
37
+ SAFE_AUTO_FIX_SCOPES = {
38
+ "headers_required",
39
+ "docstrings_required",
40
+ "snake_case",
41
+ "camel_case",
42
+ "pascal_case",
43
+ }
44
+
45
+ # Convention rules that require human review (too risky to auto-fix)
46
+ RISKY_SCOPES = {
47
+ "lines",
48
+ "file_size",
49
+ "functions",
50
+ }
51
+
52
+
53
+ @dataclass
54
+ class ProactiveFixResult:
55
+ """Result of proactive fix attempt on a single file."""
56
+
57
+ file_path: str
58
+ rule_id: str
59
+ rule_type: str
60
+ scope: str
61
+ auto_fixed: bool = False
62
+ changes_made: list[str] = field(default_factory=list)
63
+ blocked_reason: str = ""
64
+ original_content: str = ""
65
+ fixed_content: str = ""
66
+
67
+
68
+ @dataclass
69
+ class ProactiveFixReport:
70
+ """Aggregated result of proactive fix scan."""
71
+
72
+ files_scanned: int = 0
73
+ violations_found: int = 0
74
+ auto_fixed: int = 0
75
+ blocked: int = 0
76
+ results: list[ProactiveFixResult] = field(default_factory=list)
77
+
78
+ def to_dict(self) -> dict:
79
+ return {
80
+ "files_scanned": self.files_scanned,
81
+ "violations_found": self.violations_found,
82
+ "auto_fixed": self.auto_fixed,
83
+ "blocked": self.blocked,
84
+ "results": [r.__dict__ for r in self.results],
85
+ }
86
+
87
+
88
+ class ProactiveAutoFixer:
89
+ """
90
+ Checks files against charter rules and auto-fixes safe violations.
91
+
92
+ Flow:
93
+ 1. Load charter from .patchi/memory/charter.json
94
+ 2. For each file, check convention rules
95
+ 3. If safe to fix (headers, docstrings), apply fix
96
+ 4. If risky (lines, secrets), flag for human review
97
+ 5. Return report with all changes made
98
+ """
99
+
100
+ def __init__(self, root: Path, apply: bool = True):
101
+ self.root = root
102
+ self.apply = apply
103
+ self.charter = load_charter(root)
104
+ self.fix_count = 0
105
+
106
+ def check_and_fix_file(self, file_path: Path) -> list[ProactiveFixResult]:
107
+ """Check a single file against all charter rules and auto-fix safe ones."""
108
+ results = []
109
+
110
+ if not file_path.is_file():
111
+ return results
112
+
113
+ try:
114
+ content = file_path.read_text(encoding="utf-8", errors="ignore")
115
+ except OSError:
116
+ return results
117
+
118
+ rel_path = str(file_path.relative_to(self.root))
119
+
120
+ for rule in self.charter.rules:
121
+ if not rule.enabled:
122
+ continue
123
+
124
+ if rule.type == RuleType.CONVENTION:
125
+ result = self._check_convention(rule, rel_path, file_path, content)
126
+ if result:
127
+ results.append(result)
128
+ if result.auto_fixed:
129
+ self.fix_count += 1
130
+
131
+ elif rule.type == RuleType.BOUNDARY:
132
+ result = self._check_boundary(rule, rel_path, content)
133
+ if result:
134
+ results.append(result)
135
+
136
+ elif rule.type == RuleType.SECURITY:
137
+ result = self._check_security(rule, rel_path, content)
138
+ if result:
139
+ results.append(result)
140
+
141
+ elif rule.type == RuleType.STACK:
142
+ result = self._check_stack(rule, rel_path)
143
+ if result:
144
+ results.append(result)
145
+
146
+ return results
147
+
148
+ def check_files(self, file_paths: list[Path]) -> ProactiveFixReport:
149
+ """Check multiple files and auto-fix safe violations."""
150
+ report = ProactiveFixReport()
151
+
152
+ for fp in file_paths:
153
+ results = self.check_and_fix_file(fp)
154
+ if results:
155
+ report.files_scanned += 1
156
+ report.violations_found += len(results)
157
+ for r in results:
158
+ if r.auto_fixed:
159
+ report.auto_fixed += 1
160
+ elif r.blocked_reason:
161
+ report.blocked += 1
162
+ report.results.append(r)
163
+
164
+ return report
165
+
166
+ def _check_convention(
167
+ self, rule: CharterRule, rel_path: str, abs_path: Path, content: str
168
+ ) -> ProactiveFixResult | None:
169
+ """Check and auto-fix convention rules."""
170
+ result = ProactiveFixResult(
171
+ file_path=rel_path,
172
+ rule_id=rule.id,
173
+ rule_type=rule.type.value,
174
+ scope=rule.scope,
175
+ )
176
+
177
+ if rule.scope in SAFE_AUTO_FIX_SCOPES:
178
+ fixed, changes = self._auto_fix_convention(rule, abs_path, content)
179
+ if fixed:
180
+ result.auto_fixed = True
181
+ result.changes_made = changes
182
+ result.original_content = content
183
+ if self.apply:
184
+ self._write_file(abs_path, fixed)
185
+ result.fixed_content = fixed
186
+ return result
187
+ return None # No violation found
188
+
189
+ elif rule.scope in RISKY_SCOPES:
190
+ # Check if violation exists, but don't auto-fix
191
+ violated = self._check_convention_violation(rule, abs_path, content)
192
+ if violated:
193
+ result.blocked_reason = (
194
+ f"Convention violation: {rule.description} "
195
+ f"(requires manual fix)"
196
+ )
197
+ return result
198
+ return None
199
+
200
+ # Unknown scope — flag but don't fix
201
+ return None
202
+
203
+ def _check_convention_violation(
204
+ self, rule: CharterRule, abs_path: Path, content: str
205
+ ) -> bool:
206
+ """Check if a convention rule is violated."""
207
+ if rule.scope == "lines" and rule.max_value > 0:
208
+ line_count = len(content.splitlines())
209
+ return line_count > rule.max_value
210
+
211
+ if rule.scope == "file_size" and rule.max_value > 0:
212
+ return len(content.encode("utf-8")) > rule.max_value
213
+
214
+ return False
215
+
216
+ def _auto_fix_convention(
217
+ self, rule: CharterRule, abs_path: Path, content: str
218
+ ) -> tuple[str | None, list[str]]:
219
+ """Apply safe auto-fix for a convention rule.
220
+
221
+ Returns:
222
+ (fixed_content_or_None, list_of_changes_made)
223
+ """
224
+ changes = []
225
+
226
+ if rule.scope == "headers_required":
227
+ fixed = self._fix_missing_header(abs_path, content)
228
+ if fixed and fixed != content:
229
+ changes.append("Added file header")
230
+ return fixed, changes
231
+
232
+ elif rule.scope == "docstrings_required":
233
+ fixed = self._fix_missing_docstrings(abs_path, content)
234
+ if fixed and fixed != content:
235
+ changes.append("Added docstrings to functions/classes")
236
+ return fixed, changes
237
+
238
+ elif rule.scope in ("snake_case", "camel_case", "pascal_case"):
239
+ # Only flag naming issues — renaming is too risky for auto-fix
240
+ return None, []
241
+
242
+ return None, []
243
+
244
+ def _fix_missing_header(self, abs_path: Path, content: str) -> str | None:
245
+ """Add a license/author header if missing."""
246
+ if not content.strip():
247
+ return content
248
+
249
+ # Check if header already exists
250
+ first_line = content.split("\n", 1)[0].strip()
251
+ header_indicators = [
252
+ "#!",
253
+ "# Copyright",
254
+ "# License",
255
+ '"""',
256
+ "# !",
257
+ "# *",
258
+ "# -",
259
+ "/*",
260
+ "//",
261
+ "<!--",
262
+ "# coding",
263
+ ]
264
+
265
+ for indicator in header_indicators:
266
+ if first_line.startswith(indicator):
267
+ return None # Header already exists
268
+
269
+ # Determine file type for comment syntax
270
+ suffix = abs_path.suffix.lower()
271
+ comment_prefix = {
272
+ ".py": "#",
273
+ ".js": "//",
274
+ ".ts": "//",
275
+ ".jsx": "//",
276
+ ".tsx": "//",
277
+ ".mjs": "//",
278
+ ".cjs": "//",
279
+ ".go": "//",
280
+ ".java": "//",
281
+ ".c": "//",
282
+ ".cpp": "//",
283
+ ".h": "//",
284
+ ".hpp": "//",
285
+ ".rs": "//",
286
+ ".rb": "#",
287
+ ".php": "//",
288
+ ".cs": "//",
289
+ ".swift": "//",
290
+ ".scala": "//",
291
+ ".kt": "//",
292
+ }.get(suffix)
293
+
294
+ if not comment_prefix:
295
+ return None
296
+
297
+ header = (
298
+ f"{comment_prefix} Auto-fixed by Patchi\n"
299
+ f"{comment_prefix} Added missing file header\n\n"
300
+ )
301
+
302
+ return header + content
303
+
304
+ def _fix_missing_docstrings(self, abs_path: Path, content: str) -> str | None:
305
+ """Add empty docstrings to functions/classes missing them."""
306
+ suffix = abs_path.suffix.lower()
307
+
308
+ if suffix == ".py":
309
+ return self._fix_python_docstrings(content)
310
+ elif suffix in (".js", ".ts", ".jsx", ".tsx"):
311
+ return self._fix_jsdoc(content, suffix)
312
+ # Other languages: skip for safety
313
+ return None
314
+
315
+ def _fix_python_docstrings(self, content: str) -> str | None:
316
+ """Add empty docstrings to Python functions/classes missing them."""
317
+ lines = content.splitlines()
318
+ result = []
319
+ modified = False
320
+ i = 0
321
+
322
+ while i < len(lines):
323
+ result.append(lines[i])
324
+
325
+ # Check if this is a def or class line
326
+ stripped = lines[i].lstrip()
327
+ is_func = stripped.startswith("def ") or stripped.startswith("async def ")
328
+ is_class = stripped.startswith("class ")
329
+
330
+ if (is_func or is_class) and not stripped.startswith("#"):
331
+ # Look for docstring after the line (may be on next non-empty line)
332
+ has_docstring = False
333
+ j = i + 1
334
+ while j < len(lines):
335
+ next_stripped = lines[j].strip()
336
+ if next_stripped:
337
+ if next_stripped.startswith('"""') or next_stripped.startswith("'''"):
338
+ has_docstring = True
339
+ break
340
+ j += 1
341
+
342
+ if not has_docstring and j < len(lines):
343
+ # Add empty docstring on next line
344
+ indent = len(lines[i]) - len(lines[i].lstrip()) + 4
345
+ prefix = " " * indent
346
+ result.append(f'{prefix}"""Add description."""')
347
+ modified = True
348
+
349
+ i += 1
350
+
351
+ if modified:
352
+ return "\n".join(result)
353
+ return None
354
+
355
+ def _fix_jsdoc(self, content: str, suffix: str) -> str | None:
356
+ """Add empty JSDoc to JS/TS functions missing comments."""
357
+ lines = content.splitlines()
358
+ result = []
359
+ modified = False
360
+
361
+ for i, line in enumerate(lines):
362
+ result.append(line)
363
+ stripped = line.lstrip()
364
+
365
+ # Check for function declarations
366
+ is_func = (
367
+ stripped.startswith("function ")
368
+ or stripped.startswith("async function ")
369
+ or (stripped.startswith("const ") and "=>" in stripped)
370
+ or (stripped.startswith("let ") and "=>" in stripped)
371
+ or stripped.startswith("export function ")
372
+ or stripped.startswith("export async function ")
373
+ )
374
+
375
+ if is_func:
376
+ # Check if preceded by a comment
377
+ if i > 0:
378
+ prev = lines[i - 1].strip()
379
+ if prev.endswith("*/") or prev.startswith("//"):
380
+ continue
381
+
382
+ indent = len(line) - len(line.lstrip())
383
+ prefix = " " * indent
384
+ result.insert(-1, f"{prefix}/** Add description. */")
385
+ modified = True
386
+
387
+ if modified:
388
+ return "\n".join(result)
389
+ return None
390
+
391
+ def _check_boundary(
392
+ self, rule: CharterRule, rel_path: str, content: str
393
+ ) -> ProactiveFixResult | None:
394
+ """Check boundary rules — flag only, no auto-fix."""
395
+ # Parse imports from content
396
+ imports = self._extract_imports(rel_path, content)
397
+
398
+ violations = []
399
+ for src_module, imported in imports:
400
+ src_lower = src_module.lower()
401
+ imp_lower = imported.lower()
402
+
403
+ if rule.from_pattern.lower() in src_lower and rule.to_pattern.lower() in imp_lower:
404
+ violations.append((src_module, imported))
405
+
406
+ if violations:
407
+ result = ProactiveFixResult(
408
+ file_path=rel_path,
409
+ rule_id=rule.id,
410
+ rule_type=rule.type.value,
411
+ scope="boundary",
412
+ )
413
+ result.blocked_reason = (
414
+ f"Boundary violation: {rule.description} "
415
+ f"({violations[0][0]} imports {violations[0][1]})"
416
+ )
417
+ return result
418
+
419
+ return None
420
+
421
+ def _extract_imports(self, file_path: str, content: str) -> list[tuple[str, str]]:
422
+ """Extract import relationships from source code.
423
+
424
+ Returns list of (source_module, imported_module) tuples.
425
+ """
426
+ imports = []
427
+ suffix = Path(file_path).suffix.lower()
428
+
429
+ if suffix == ".py":
430
+ imports.extend(self._extract_python_imports(content))
431
+ elif suffix in (".js", ".ts", ".jsx", ".tsx", ".mjs"):
432
+ imports.extend(self._extract_js_imports(content))
433
+ elif suffix == ".go":
434
+ imports.extend(self._extract_go_imports(content))
435
+
436
+ return imports
437
+
438
+ def _extract_python_imports(self, content: str) -> list[tuple[str, str]]:
439
+ """Extract Python import statements."""
440
+ imports = []
441
+ for line in content.splitlines():
442
+ stripped = line.strip()
443
+ if stripped.startswith("from ") and " import " in stripped:
444
+ # from X import Y
445
+ parts = stripped.split(" import ", 1)
446
+ if len(parts) == 2:
447
+ module = parts[0].replace("from ", "").strip()
448
+ imports.append((module, module))
449
+ elif stripped.startswith("import "):
450
+ # import X
451
+ module = stripped.replace("import ", "").strip().split(",")[0].strip()
452
+ imports.append((module, module))
453
+ return imports
454
+
455
+ def _extract_js_imports(self, content: str) -> list[tuple[str, str]]:
456
+ """Extract JavaScript/TypeScript import statements."""
457
+ imports = []
458
+ for line in content.splitlines():
459
+ stripped = line.strip()
460
+ if "from '" in stripped or 'from "' in stripped:
461
+ parts = stripped.split("from ")[-1].strip().strip("'\"")
462
+ imports.append((parts, parts))
463
+ elif stripped.startswith("import '") or stripped.startswith('import "'):
464
+ mod = stripped.replace("import ", "").strip().strip("'\"")
465
+ imports.append((mod, mod))
466
+ return imports
467
+
468
+ def _extract_go_imports(self, content: str) -> list[tuple[str, str]]:
469
+ """Extract Go import statements."""
470
+ imports = []
471
+ in_import = False
472
+ for line in content.splitlines():
473
+ stripped = line.strip()
474
+ if stripped.startswith("import ("):
475
+ in_import = True
476
+ continue
477
+ if in_import and stripped == ")":
478
+ in_import = False
479
+ continue
480
+ if in_import and stripped.startswith('"'):
481
+ pkg = stripped.strip('"')
482
+ imports.append((pkg, pkg))
483
+ return imports
484
+
485
+ def _check_security(
486
+ self, rule: CharterRule, rel_path: str, content: str
487
+ ) -> ProactiveFixResult | None:
488
+ """Check security rules — flag only, no auto-fix."""
489
+ if not rule.keywords:
490
+ return None
491
+
492
+ content_lower = content.lower()
493
+ for keyword in rule.keywords:
494
+ if keyword.lower() in content_lower:
495
+ result = ProactiveFixResult(
496
+ file_path=rel_path,
497
+ rule_id=rule.id,
498
+ rule_type=rule.type.value,
499
+ scope="security",
500
+ )
501
+ result.blocked_reason = (
502
+ f"Security violation: {rule.description} "
503
+ f"(found '{keyword}')"
504
+ )
505
+ return result
506
+
507
+ return None
508
+
509
+ def _check_stack(
510
+ self, rule: CharterRule, rel_path: str
511
+ ) -> ProactiveFixResult | None:
512
+ """Check stack rules — flag only, no auto-fix."""
513
+ if not rule.allowed_languages:
514
+ return None
515
+
516
+ # Map file extensions to language names
517
+ ext_to_lang = {
518
+ ".py": "python",
519
+ ".js": "javascript",
520
+ ".ts": "typescript",
521
+ ".jsx": "javascript",
522
+ ".tsx": "typescript",
523
+ ".go": "go",
524
+ ".rs": "rust",
525
+ ".java": "java",
526
+ ".cs": "csharp",
527
+ }
528
+
529
+ suffix = Path(rel_path).suffix.lower()
530
+ lang = ext_to_lang.get(suffix, "")
531
+
532
+ if lang and lang not in rule.allowed_languages:
533
+ result = ProactiveFixResult(
534
+ file_path=rel_path,
535
+ rule_id=rule.id,
536
+ rule_type=rule.type.value,
537
+ scope="stack",
538
+ )
539
+ result.blocked_reason = (
540
+ f"Stack violation: {rule.description} "
541
+ f"(found {lang} file, allowed: {', '.join(rule.allowed_languages)})"
542
+ )
543
+ return result
544
+
545
+ return None
546
+
547
+ def _write_file(self, path: Path, content: str) -> None:
548
+ """Write content to file safely."""
549
+ try:
550
+ path.write_text(content, encoding="utf-8")
551
+ _log.info("Auto-fixed: %s", path)
552
+ except OSError as e:
553
+ _log.error("Failed to write %s: %s", path, e)
554
+
555
+ def get_fix_history(self) -> list[dict]:
556
+ """Get recent proactive fixes from memory."""
557
+ from patchi.core import memory as mem
558
+
559
+ history = mem.list_issues(self.root)
560
+ return [
561
+ item for item in history
562
+ if item.get("type") == "proactive_fix"
563
+ ]
564
+
565
+ def save_fix_history(self, report: ProactiveFixReport) -> None:
566
+ """Save proactive fix results to memory."""
567
+ from patchi.core import memory as mem
568
+
569
+ entry = {
570
+ "type": "proactive_fix",
571
+ "files_scanned": report.files_scanned,
572
+ "violations_found": report.violations_found,
573
+ "auto_fixed": report.auto_fixed,
574
+ "blocked": report.blocked,
575
+ "details": [
576
+ {
577
+ "file": r.file_path,
578
+ "rule": r.rule_id,
579
+ "scope": r.scope,
580
+ "auto_fixed": r.auto_fixed,
581
+ }
582
+ for r in report.results
583
+ ],
584
+ }
585
+ mem.save_issue(entry, self.root)
586
+
587
+
588
+ # ── Convenience Functions ─────────────────────────────────────────────────────
589
+
590
+
591
+ def proactive_fix_files(
592
+ root: Path,
593
+ file_paths: list[Path],
594
+ apply: bool = True,
595
+ ) -> ProactiveFixReport:
596
+ """Auto-fix charter violations in the given files."""
597
+ fixer = ProactiveAutoFixer(root, apply=apply)
598
+ report = fixer.check_files(file_paths)
599
+ fixer.save_fix_history(report)
600
+ return report
601
+
602
+
603
+ def proactive_fix_path(
604
+ root: Path,
605
+ path: Path,
606
+ apply: bool = True,
607
+ ) -> list[ProactiveFixResult]:
608
+ """Auto-fix charter violations in a single file."""
609
+ fixer = ProactiveAutoFixer(root, apply=apply)
610
+ results = fixer.check_and_fix_file(path)
611
+ if results:
612
+ report = ProactiveFixReport(
613
+ files_scanned=1,
614
+ violations_found=len(results),
615
+ auto_fixed=sum(1 for r in results if r.auto_fixed),
616
+ blocked=sum(1 for r in results if r.blocked_reason),
617
+ results=results,
618
+ )
619
+ fixer.save_fix_history(report)
620
+ return results