spec_driven_docs/gates/
ki_filename_shape.rs1use crate::domain::finding::Finding;
9use crate::domain::rule_id::RuleId;
10use crate::gates::{GateCtx, GateResult, Violation};
11
12pub const CITES: &[RuleId] = &[RuleId::CaseIdIsASlug];
14
15const RULE: RuleId = RuleId::CaseIdIsASlug;
16
17pub fn run(_ctx: &GateCtx, files: &[String]) -> GateResult {
23 let mut violations = Vec::new();
24 for file in files {
25 let basename = file.rsplit('/').next().unwrap_or(file);
26 let Some(after_prefix) = basename.strip_prefix("KI-") else {
27 violations.push(Violation::Finding(Finding::on_file(
28 RULE,
29 file,
30 "no KI- prefix",
31 )));
32 continue;
33 };
34 let Some(slug) = after_prefix.strip_suffix(".md") else {
35 violations.push(Violation::Finding(Finding::on_file(
36 RULE,
37 file,
38 "not a markdown file",
39 )));
40 continue;
41 };
42 if slug.as_bytes().first().is_some_and(u8::is_ascii_digit) {
43 violations.push(Violation::Finding(Finding::on_file(
44 RULE,
45 file,
46 "a slug, not a counter",
47 )));
48 } else if slug.is_empty()
49 || !slug
50 .bytes()
51 .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
52 {
53 violations.push(Violation::Finding(Finding::on_file(
54 RULE,
55 file,
56 "the slug is lowercase, digits and hyphens",
57 )));
58 }
59 }
60 Ok(violations)
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 fn run_on(files: &[&str]) -> Vec<String> {
68 let ctx = GateCtx::new(".");
69 let files: Vec<String> = files.iter().map(ToString::to_string).collect();
70 run(&ctx, &files)
71 .unwrap()
72 .iter()
73 .map(ToString::to_string)
74 .collect()
75 }
76
77 #[test]
78 fn accepts_a_slug_with_an_inner_digit() {
79 assert!(run_on(&["_docs/reference/known-issues/KI-vendor-500.md"]).is_empty());
80 }
81
82 #[test]
83 fn rejects_a_counter() {
84 let out = run_on(&["_docs/reference/known-issues/KI-001-vendor.md"]);
85 assert_eq!(out.len(), 1);
86 assert!(out[0].contains("known-issues:case-id-is-a-slug"));
87 assert!(out[0].ends_with(": a slug, not a counter"));
88 }
89
90 #[test]
91 fn rejects_missing_prefix_wrong_extension_and_bad_charset() {
92 assert!(run_on(&["x/ISSUE-a.md"])[0].ends_with(": no KI- prefix"));
93 assert!(run_on(&["x/KI-a.txt"])[0].ends_with(": not a markdown file"));
94 assert!(
95 run_on(&["x/KI-Weird.md"])[0].ends_with(": the slug is lowercase, digits and hyphens")
96 );
97 }
98}