auto_commit/api/
client.rs

1use anyhow::Result;
2use async_trait::async_trait;
3
4/// Trait for LLM clients that can generate commit messages
5#[async_trait]
6pub trait LlmClient: Send + Sync {
7    /// Generate a commit message from a git diff
8    ///
9    /// # Arguments
10    /// * `diff` - The git diff to analyze
11    /// * `template` - Optional template for the commit message format
12    ///
13    /// # Returns
14    /// A tuple of (title, description) for the commit message
15    async fn generate_commit_message(
16        &self,
17        diff: &str,
18        template: Option<&str>,
19    ) -> Result<(String, String)>;
20
21    /// Get the provider name for display
22    fn provider_name(&self) -> &str;
23}
24
25/// Build prompt for commit message generation
26pub fn build_prompt(diff: &str, template: Option<&str>) -> String {
27    let rules = template.unwrap_or(DEFAULT_PROMPT_TEMPLATE);
28
29    format!(
30        "以下のGit diffを分析して、適切なコミットメッセージを生成してください。\n\n\
31        ## コミットメッセージのルール\n\
32        {}\n\n\
33        ## 重要な指示\n\
34        - コミットメッセージのみを出力してください(説明や補足は不要)\n\
35        - 1行目はタイトル、空行を挟んで本文を記述\n\
36        - prefixとemojiを適切に選択してください\n\n\
37        ## Git diff\n\
38        ```\n{}\n```",
39        rules, diff
40    )
41}
42
43/// Parse commit message into title and description
44pub fn parse_commit_message(message: &str) -> (String, String) {
45    let parts: Vec<&str> = message.splitn(2, "\n\n").collect();
46    let title = parts[0].trim().to_string();
47    let description = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string();
48    (title, description)
49}
50
51const DEFAULT_PROMPT_TEMPLATE: &str = r#"以下のGit diffを分析して、適切なコミットメッセージを生成してください。
52
53コミットメッセージは以下のフォーマットで生成してください:
541行目: コミットタイトル(50文字以内、prefix: を含む)
552行目: 空行
563行目以降: 詳細説明(必要に応じて)
57
58使用可能なprefix:
59- feat: 新機能
60- fix: バグ修正
61- docs: ドキュメント
62- style: フォーマット
63- refactor: リファクタリング
64- test: テスト
65- chore: ビルド/CI"#;
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn test_parse_commit_message_with_description() {
73        let message = "feat: Add new feature\n\nThis is the description";
74        let (title, desc) = parse_commit_message(message);
75        assert_eq!(title, "feat: Add new feature");
76        assert_eq!(desc, "This is the description");
77    }
78
79    #[test]
80    fn test_parse_commit_message_without_description() {
81        let message = "fix: Quick fix";
82        let (title, desc) = parse_commit_message(message);
83        assert_eq!(title, "fix: Quick fix");
84        assert_eq!(desc, "");
85    }
86
87    #[test]
88    fn test_build_prompt_with_template() {
89        let diff = "+added line";
90        let template = "Custom template";
91        let prompt = build_prompt(diff, Some(template));
92        assert!(prompt.contains("Custom template"));
93        assert!(prompt.contains("+added line"));
94    }
95
96    #[test]
97    fn test_build_prompt_without_template() {
98        let diff = "+added line";
99        let prompt = build_prompt(diff, None);
100        assert!(prompt.contains("使用可能なprefix"));
101        assert!(prompt.contains("+added line"));
102    }
103}