Skip to main content

koala_drift/
check.rs

1use koala_core::invariant::Context;
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Severity {
6    /// Blocks the PR. Either fix the claim or fix the code.
7    Hard,
8    /// Reported but never blocks. Reserved for sleep/dormancy signals
9    /// (see ADR-0010).
10    Advisory,
11}
12
13impl Severity {
14    pub fn label(&self) -> &'static str {
15        match self {
16            Self::Hard => "✗",
17            Self::Advisory => "⚠",
18        }
19    }
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum FindingKind {
24    /// Acceptance criterion points to a path that does not exist.
25    AcceptanceTestRefMissing,
26    /// `[ADR-NNNN]` reference cannot be resolved to a decision file.
27    AdrRefDangling,
28    /// Reference targets an ADR with `status: superseded`.
29    AdrRefSuperseded { superseder: Option<String> },
30    /// Accepted ADR has zero inbound references in the wiki tree.
31    AdrDormant,
32    /// Gap in ADR id sequence — implies an ADR file was deleted (ADR-0008).
33    AdrIdGap { missing: u32 },
34    /// Auto-generated Tier 1 file's body checksum disagrees with the
35    /// embedded `<!-- Checksum-of-body-below -->` header.
36    Tier1Tampered { expected: String, actual: String },
37    /// A Tier 2/3 file scaffolded by `koala-core init` still contains
38    /// an unsubstituted `<...>` placeholder — the file was never filled
39    /// in.
40    TemplatePlaceholderUnfilled,
41    /// A feature whose frontmatter claims `status: done` still has an
42    /// unchecked `- [ ]` item in its `## Acceptance criteria` section.
43    AcceptanceItemUnchecked,
44    /// The ADR supersede graph is not clean — a cycle, dangling target,
45    /// status/pointer mismatch, or asymmetric link reported by
46    /// `koala_adr::validate` (see ADR-0018).
47    AdrGraphUnclean,
48}
49
50impl FindingKind {
51    pub fn short(&self) -> &'static str {
52        match self {
53            Self::AcceptanceTestRefMissing => "acceptance test ref missing",
54            Self::AdrRefDangling => "ADR reference does not resolve",
55            Self::AdrRefSuperseded { .. } => "ADR reference is superseded",
56            Self::AdrDormant => "ADR has no inbound references",
57            Self::AdrIdGap { .. } => "ADR id is missing — file was deleted",
58            Self::Tier1Tampered { .. } => "Tier 1 file body edited by hand",
59            Self::TemplatePlaceholderUnfilled => "scaffolded placeholder never filled in",
60            Self::AcceptanceItemUnchecked => "done feature has an unchecked acceptance item",
61            Self::AdrGraphUnclean => "ADR supersede graph is not clean",
62        }
63    }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct Finding {
68    pub check_id: &'static str,
69    /// Wiki file the claim lives in, relative to the repo root for display.
70    pub file: PathBuf,
71    /// 1-indexed line number of the claim.
72    pub line: usize,
73    /// Verbatim claim text (trimmed).
74    pub claim: String,
75    pub kind: FindingKind,
76    pub severity: Severity,
77    pub fix_hint: Option<String>,
78}
79
80pub trait Check: Send + Sync {
81    fn id(&self) -> &'static str;
82    fn intent(&self) -> &'static str;
83    fn run(&self, ctx: &Context) -> Vec<Finding>;
84}