git_commit_sage/
lib.rs

1pub mod ai;
2pub mod config;
3pub mod error;
4pub mod git;
5pub mod protocol;
6
7pub use crate::ai::AiClient;
8pub use crate::config::{Config, AiConfig, GitConfig, CommitConfig, AVAILABLE_MODELS};
9pub use crate::error::{Error, Result};
10pub use crate::git::GitRepo;
11pub use crate::protocol::{
12    ModelProvider, CommitMessageGenerator, ModelContext, GenerationConfig,
13    Message, TogetherAiProvider,
14};
15
16#[cfg(test)]
17mod tests {
18    use super::*;
19    use test_case::test_case;
20
21    #[test_case("feat: add new feature", true)]
22    #[test_case("fix(core): resolve issue", true)]
23    #[test_case("random message", false)]
24    fn test_is_conventional_commit(message: &str, expected: bool) {
25        let is_conventional = is_conventional_commit(message);
26        assert_eq!(is_conventional, expected);
27    }
28}
29
30/// Checks if a commit message follows the Conventional Commits specification
31pub fn is_conventional_commit(message: &str) -> bool {
32    let conventional_types = [
33        "feat", "fix", "docs", "style", "refactor",
34        "perf", "test", "build", "ci", "chore", "revert"
35    ];
36
37    // Basic format: <type>[optional scope]: <description>
38    let parts: Vec<&str> = message.splitn(2, ": ").collect();
39    if parts.len() != 2 {
40        return false;
41    }
42
43    let type_part = parts[0];
44    
45    // Check if there's a scope
46    let commit_type = if type_part.contains('(') {
47        type_part.split('(').next().unwrap_or("")
48    } else {
49        type_part
50    };
51
52    conventional_types.contains(&commit_type)
53}