@@ -70,3 +70,73 @@ pub fn build_system_prompt(cfg: &AppConfig) -> String {
7070
7171 parts. join ( "\n \n " )
7272}
73+
74+ /// Strip common LLM artifacts from the raw response so only the commit message remains.
75+ ///
76+ /// Handles:
77+ /// - Markdown code fences (``` or ```commit / ```text / etc.)
78+ /// - Leading label lines ("Here is your commit message:", "Commit message:", etc.)
79+ /// - Surrounding quotation marks
80+ pub fn clean_commit_message ( raw : & str ) -> String {
81+ let s = raw. trim ( ) ;
82+
83+ // Strip markdown code fences
84+ let s = strip_code_fence ( s) ;
85+
86+ // Strip a leading label line (everything before the first blank line or
87+ // the first line that looks like a conventional commit / gitmoji prefix).
88+ let s = strip_label_prefix ( s) ;
89+
90+ // Strip surrounding straight or curly quotes
91+ let s = strip_surrounding_quotes ( s) ;
92+
93+ s. trim ( ) . to_string ( )
94+ }
95+
96+ fn strip_code_fence ( s : & str ) -> & str {
97+ // Match opening fence with optional language tag (e.g., ```commit, ```text)
98+ if let Some ( inner) = s. strip_prefix ( "```" ) {
99+ // Skip the language tag on the first line
100+ let after_tag = inner. trim_start_matches ( |c : char | c. is_alphanumeric ( ) || c == '-' ) ;
101+ // Must start with a newline after the tag
102+ if let Some ( body) = after_tag. strip_prefix ( '\n' ) {
103+ if let Some ( end) = body. rfind ( "```" ) {
104+ return body[ ..end] . trim ( ) ;
105+ }
106+ }
107+ }
108+ s
109+ }
110+
111+ fn strip_label_prefix ( s : & str ) -> & str {
112+ // Common prefixes LLMs put before the actual message
113+ let label_patterns: & [ & str ] = & [
114+ "commit message:" ,
115+ "here is the commit message:" ,
116+ "here's the commit message:" ,
117+ "here is your commit message:" ,
118+ "here's your commit message:" ,
119+ "generated commit message:" ,
120+ "suggested commit message:" ,
121+ "the commit message:" ,
122+ ] ;
123+
124+ let lower = s. to_lowercase ( ) ;
125+ for pat in label_patterns {
126+ if let Some ( rest) = lower. strip_prefix ( pat) {
127+ // Trim blank lines / whitespace after the label
128+ return s[ pat. len ( ) ..] [ rest. len ( ) - rest. trim_start ( ) . len ( ) ..] . trim_start ( ) ;
129+ }
130+ }
131+ s
132+ }
133+
134+ fn strip_surrounding_quotes ( s : & str ) -> & str {
135+ let quote_pairs: & [ ( char , char ) ] = & [ ( '"' , '"' ) , ( '\'' , '\'' ) , ( '\u{201c}' , '\u{201d}' ) ] ;
136+ for & ( open, close) in quote_pairs {
137+ if s. starts_with ( open) && s. ends_with ( close) && s. len ( ) > 1 {
138+ return & s[ open. len_utf8 ( ) ..s. len ( ) - close. len_utf8 ( ) ] ;
139+ }
140+ }
141+ s
142+ }
0 commit comments