lean_ctx/core/skillify/
gate.rs1use super::candidate::SkillCandidate;
9
10#[derive(Debug, Clone, PartialEq)]
12pub enum Verdict {
13 Keep,
15 Skip(String),
17}
18
19const MIN_BODY_LEN: usize = 25;
21
22const NOISE: [&str; 9] = [
24 "fixed bug",
25 "updated code",
26 "wip",
27 "work in progress",
28 "todo",
29 "refactored",
30 "cleanup",
31 "minor change",
32 "various changes",
33];
34
35fn is_generic(body: &str) -> bool {
36 let trimmed = body.trim().to_ascii_lowercase();
37 NOISE.iter().any(|n| {
38 trimmed == *n || (trimmed.starts_with(n) && trimmed.chars().count() < n.len() + 12)
39 })
40}
41
42pub fn judge(c: &SkillCandidate, min_confidence: f32, min_recurrence: u32) -> Verdict {
44 if c.body.chars().count() < MIN_BODY_LEN {
45 return Verdict::Skip("too short".to_string());
46 }
47 if is_generic(&c.body) {
48 return Verdict::Skip("generic / one-off phrasing".to_string());
49 }
50 if c.recurrence >= min_recurrence || c.confidence >= min_confidence {
52 Verdict::Keep
53 } else {
54 Verdict::Skip(format!(
55 "one-off (recurrence {} < {min_recurrence}, confidence {:.2} < {min_confidence:.2})",
56 c.recurrence, c.confidence
57 ))
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 fn cand(body: &str, recurrence: u32, confidence: f32) -> SkillCandidate {
66 SkillCandidate {
67 slug: "s".into(),
68 title: "t".into(),
69 body: body.into(),
70 category: "decision".into(),
71 recurrence,
72 confidence,
73 sources: vec![],
74 }
75 }
76
77 #[test]
78 fn rejects_too_short() {
79 assert!(matches!(
80 judge(&cand("short", 5, 0.9), 0.7, 2),
81 Verdict::Skip(_)
82 ));
83 }
84
85 #[test]
86 fn rejects_generic() {
87 assert!(matches!(
88 judge(&cand("fixed bug", 5, 0.9), 0.7, 2),
89 Verdict::Skip(_)
90 ));
91 }
92
93 #[test]
94 fn keeps_recurring_pattern() {
95 let c = cand(
96 "Always run lean-ctx stop before building the binary.",
97 2,
98 0.5,
99 );
100 assert_eq!(judge(&c, 0.7, 2), Verdict::Keep);
101 }
102
103 #[test]
104 fn keeps_high_confidence_single() {
105 let c = cand(
106 "Always run lean-ctx stop before building the binary.",
107 1,
108 0.85,
109 );
110 assert_eq!(judge(&c, 0.7, 2), Verdict::Keep);
111 }
112
113 #[test]
114 fn skips_low_signal_oneoff() {
115 let c = cand(
116 "Always run lean-ctx stop before building the binary.",
117 1,
118 0.5,
119 );
120 assert!(matches!(judge(&c, 0.7, 2), Verdict::Skip(_)));
121 }
122}