ailint_core/rules/semantic/
negative_constraint_overload.rs1use crate::parser::{DocumentContent, ParsedDocument};
6use crate::rules::semantic::AIL104;
7use crate::rules::{Rule, RuleContext, RuleId, Severity, Violation};
8
9#[derive(Debug, Default)]
11pub struct NegativeConstraintOverloadRule;
12
13impl Rule for NegativeConstraintOverloadRule {
14 fn id(&self) -> RuleId {
15 AIL104
16 }
17
18 fn default_severity(&self) -> Severity {
19 Severity::Warning
20 }
21
22 fn run(&self, doc: &ParsedDocument, ctx: &RuleContext<'_>) -> Vec<Violation> {
23 let md = match &doc.content {
24 DocumentContent::Markdown(m) => m,
25 _ => return Vec::new(),
26 };
27
28 if md.list_items.len() < 5 {
29 return Vec::new();
30 }
31
32 let negative_prefixes = [
34 "do not", "don't", "never", "avoid", "stop ", "no ", "must not",
35 ];
36
37 let mut negative_count = 0;
38 for item in &md.list_items {
39 let lower_text = item
40 .text
41 .trim_start_matches(['*', '_', '`'])
42 .trim()
43 .to_lowercase();
44 if negative_prefixes.iter().any(|p| lower_text.starts_with(p)) {
45 negative_count += 1;
46 }
47 }
48
49 if negative_count > md.list_items.len() / 2 {
50 let mut v = Violation::new(
51 AIL104,
52 ctx.severity,
53 doc.path.clone(),
54 format!(
55 "Negative constraint overload: {} out of {} list items are negative constraints. LLMs perform better with affirmative instruction phrasing.",
56 negative_count,
57 md.list_items.len()
58 ),
59 );
60 v.fix_hint = Some("Refactor constraints to state what the agent *should* do, rather than exhaustive lists of prohibitions.".to_string());
61 vec![v]
62 } else {
63 Vec::new()
64 }
65 }
66}