lean_ctx/core/code_health/
gate.rs1use super::{GateMode, cognitive_delta, format_gate_notice, worst_regression};
10use crate::core::config::Config;
11
12pub enum GateOutcome {
14 Allow(Option<String>),
16 Block(String),
18}
19
20pub fn evaluate(old: &str, new: &str, ext: &str) -> GateOutcome {
23 let cfg = Config::load();
24 evaluate_with(
25 old,
26 new,
27 ext,
28 GateMode::parse(&cfg.code_health.gate),
29 cfg.code_health.cognitive_threshold,
30 )
31}
32
33pub fn evaluate_with(
35 old: &str,
36 new: &str,
37 ext: &str,
38 mode: GateMode,
39 threshold: u32,
40) -> GateOutcome {
41 if matches!(mode, GateMode::Off) {
42 return GateOutcome::Allow(None);
43 }
44 let deltas = cognitive_delta(old, new, ext);
45 let Some(worst) = worst_regression(&deltas, threshold) else {
46 return GateOutcome::Allow(None);
47 };
48 let notice = format_gate_notice(worst, threshold);
49 if matches!(mode, GateMode::Block) && worst.crosses_threshold(threshold) {
51 GateOutcome::Block(format!(
52 "{notice}\n(set [code_health] gate=\"warn\" to allow)"
53 ))
54 } else {
55 GateOutcome::Allow(Some(notice))
56 }
57}
58
59#[cfg(all(test, feature = "tree-sitter"))]
60mod tests {
61 use super::*;
62
63 const FLAT: &str = "fn f(a: bool) { if a {} }";
64 const DEEP: &str = "fn f(a: bool) { if a { if a { if a { if a { if a { if a {} } } } } } }";
66
67 #[test]
68 fn off_mode_allows_silently() {
69 match evaluate_with(FLAT, DEEP, "rs", GateMode::Off, 15) {
70 GateOutcome::Allow(None) => {}
71 _ => panic!("off mode must allow with no notice"),
72 }
73 }
74
75 #[test]
76 fn warn_mode_allows_with_notice() {
77 match evaluate_with(FLAT, DEEP, "rs", GateMode::Warn, 15) {
78 GateOutcome::Allow(Some(notice)) => assert!(notice.contains("[CODE HEALTH]")),
79 _ => panic!("warn mode must allow with a notice"),
80 }
81 }
82
83 #[test]
84 fn block_mode_blocks_threshold_crossing() {
85 match evaluate_with(FLAT, DEEP, "rs", GateMode::Block, 15) {
86 GateOutcome::Block(reason) => assert!(reason.contains("[CODE HEALTH]")),
87 GateOutcome::Allow(_) => panic!("block mode must block a clean→over edit"),
88 }
89 }
90
91 #[test]
92 fn no_regression_allows_silently() {
93 match evaluate_with(FLAT, FLAT, "rs", GateMode::Block, 15) {
94 GateOutcome::Allow(None) => {}
95 _ => panic!("unchanged complexity must allow silently"),
96 }
97 }
98}