git_iris/commit/
prompt.rs

1use crate::common::get_combined_instructions;
2use crate::config::Config;
3use crate::context::{
4    ChangeType, CommitContext, GeneratedMessage, GeneratedReview, ProjectMetadata, RecentCommit,
5    StagedFile,
6};
7use crate::gitmoji::{apply_gitmoji, get_gitmoji_list};
8
9use super::relevance::RelevanceScorer;
10use crate::log_debug;
11use std::collections::HashMap;
12
13pub fn create_system_prompt(config: &Config) -> anyhow::Result<String> {
14    let commit_schema = schemars::schema_for!(GeneratedMessage);
15    let commit_schema_str = serde_json::to_string_pretty(&commit_schema)?;
16
17    let mut prompt = String::from(
18        "You are an AI assistant specializing in creating high-quality, professional Git commit messages. \
19        Your task is to generate clear, concise, and informative commit messages based solely on the provided context.
20        
21        Work step-by-step and follow these guidelines exactly:
22
23        1. Use the imperative mood in the subject line (e.g., 'Add feature' not 'Added feature').
24        2. Limit the subject line to 50 characters if possible, but never exceed 72 characters.
25        3. Capitalize the subject line.
26        4. Do not end the subject line with a period.
27        5. Separate subject from body with a blank line.
28        6. Wrap the body at 72 characters.
29        7. Use the body to explain what changes were made and their impact, and how they were implemented.
30        8. Be specific and avoid vague language.
31        9. Focus on the concrete changes and their effects, not assumptions about intent.
32        10. If the changes are part of a larger feature or fix, state this fact if evident from the context.
33        11. For non-trivial changes, include a brief explanation of the change's purpose if clearly indicated in the context.
34        12. Do not include a conclusion or end summary section.
35        13. Avoid common cliché words (like 'enhance', 'streamline', 'leverage', etc) and phrases.
36        14. Don't mention filenames in the subject line unless absolutely necessary.
37        15. Only describe changes that are explicitly shown in the provided context.
38        16. If the purpose or impact of a change is not clear from the context, focus on describing the change itself without inferring intent.
39        17. Do not use phrases like 'seems to', 'appears to', or 'might be' - only state what is certain based on the context.
40        18. If there's not enough information to create a complete, authoritative message, state only what can be confidently determined from the context.
41        19. NO YAPPING!
42
43        Be sure to quote newlines and any other control characters in your response.
44
45        The message should be based entirely on the information provided in the context,
46        without any speculation or assumptions.
47      ");
48
49    prompt.push_str(get_combined_instructions(config).as_str());
50
51    if config.use_gitmoji {
52        prompt.push_str(
53            "\n\nUse a single gitmoji at the start of the commit message. \
54          Choose the most relevant emoji from the following list:\n\n",
55        );
56        prompt.push_str(&get_gitmoji_list());
57    }
58
59    prompt.push_str("
60        Your response must be a valid JSON object with the following structure:
61
62        {
63          \"emoji\": \"string or null\",
64          \"title\": \"string\",
65          \"message\": \"string\"
66        }
67
68        Follow these steps to generate the commit message:
69
70        1. Analyze the provided context, including staged changes, recent commits, and project metadata.
71        2. Identify the main purpose of the commit based on the changes.
72        3. Create a concise and descriptive title (subject line) for the commit.
73        4. If using emojis (false unless stated below), select the most appropriate one for the commit type.
74        5. Write a detailed message body explaining the changes, their impact, and any other relevant information.
75        6. Ensure the message adheres to the guidelines above, and follows all of the additional instructions provided.
76        7. Construct the final JSON object with the emoji (if applicable), title, and message.
77
78         Here's a minimal example of the expected output format:
79
80        {
81          \"emoji\": \"✨\",
82          \"title\": \"Add user authentication feature\",
83          \"message\": \"Implement user authentication using JWT tokens\\n\\n- Add login and registration endpoints\\n- Create middleware for token verification\\n- Update user model to include password hashing\\n- Add unit tests for authentication functions\"
84        }
85
86        Ensure that your response is a valid JSON object matching this structure. Include an empty string for the emoji if not using one.
87        "
88    );
89
90    prompt.push_str(&commit_schema_str);
91
92    Ok(prompt)
93}
94
95pub fn create_user_prompt(context: &CommitContext) -> String {
96    let scorer = RelevanceScorer::new();
97    let relevance_scores = scorer.score(context);
98    let detailed_changes = format_detailed_changes(&context.staged_files, &relevance_scores);
99
100    let prompt = format!(
101        "Based on the following context, generate a Git commit message:\n\n\
102        Branch: {}\n\n\
103        Recent commits:\n{}\n\n\
104        Staged changes:\n{}\n\n\
105        Project metadata:\n{}\n\n\
106        Detailed changes:\n{}",
107        context.branch,
108        format_recent_commits(&context.recent_commits),
109        format_staged_files(&context.staged_files, &relevance_scores),
110        format_project_metadata(&context.project_metadata),
111        detailed_changes
112    );
113
114    log_debug!(
115        "Generated commit prompt for {} files ({} added, {} modified, {} deleted)",
116        context.staged_files.len(),
117        context
118            .staged_files
119            .iter()
120            .filter(|f| matches!(f.change_type, ChangeType::Added))
121            .count(),
122        context
123            .staged_files
124            .iter()
125            .filter(|f| matches!(f.change_type, ChangeType::Modified))
126            .count(),
127        context
128            .staged_files
129            .iter()
130            .filter(|f| matches!(f.change_type, ChangeType::Deleted))
131            .count()
132    );
133
134    prompt
135}
136
137fn format_recent_commits(commits: &[RecentCommit]) -> String {
138    commits
139        .iter()
140        .map(|commit| format!("{} - {}", &commit.hash[..7], commit.message))
141        .collect::<Vec<_>>()
142        .join("\n")
143}
144
145fn format_staged_files(files: &[StagedFile], relevance_scores: &HashMap<String, f32>) -> String {
146    files
147        .iter()
148        .map(|file| {
149            let relevance = relevance_scores.get(&file.path).unwrap_or(&0.0);
150            format!(
151                "{} ({:.2}) - {}",
152                file.path,
153                relevance,
154                format_change_type(&file.change_type)
155            )
156        })
157        .collect::<Vec<_>>()
158        .join("\n")
159}
160
161fn format_project_metadata(metadata: &ProjectMetadata) -> String {
162    format!(
163        "Language: {}\nFramework: {}\nDependencies: {}",
164        metadata.language.as_deref().unwrap_or("None"),
165        metadata.framework.as_deref().unwrap_or("None"),
166        metadata.dependencies.join(", ")
167    )
168}
169
170fn format_detailed_changes(
171    files: &[StagedFile],
172    relevance_scores: &HashMap<String, f32>,
173) -> String {
174    let mut all_sections = Vec::new();
175
176    // Add a statistical summary at the top
177    let added_count = files
178        .iter()
179        .filter(|f| matches!(f.change_type, ChangeType::Added))
180        .count();
181    let modified_count = files
182        .iter()
183        .filter(|f| matches!(f.change_type, ChangeType::Modified))
184        .count();
185    let deleted_count = files
186        .iter()
187        .filter(|f| matches!(f.change_type, ChangeType::Deleted))
188        .count();
189
190    let summary = format!(
191        "CHANGE SUMMARY:\n- {} file(s) added\n- {} file(s) modified\n- {} file(s) deleted\n- {} total file(s) changed",
192        added_count,
193        modified_count,
194        deleted_count,
195        files.len()
196    );
197    all_sections.push(summary);
198
199    // First section: File summaries with diffs
200    let diff_section = files
201        .iter()
202        .map(|file| {
203            let relevance = relevance_scores.get(&file.path).unwrap_or(&0.0);
204            // Add emoji indicators for change types
205            let change_indicator = match file.change_type {
206                ChangeType::Added => "➕",
207                ChangeType::Modified => "✏️",
208                ChangeType::Deleted => "🗑️",
209            };
210
211            format!(
212                "{} File: {} (Relevance: {:.2})\nChange Type: {}\nAnalysis:\n{}\n\nDiff:\n{}",
213                change_indicator,
214                file.path,
215                relevance,
216                format_change_type(&file.change_type),
217                file.analysis.join("\n"),
218                file.diff
219            )
220        })
221        .collect::<Vec<_>>()
222        .join("\n\n---\n\n");
223
224    all_sections.push(format!(
225        "=== DIFFS ({} files) ===\n\n{}",
226        files.len(),
227        diff_section
228    ));
229
230    // Second section: Full file contents (only for added/modified files)
231    let content_files: Vec<_> = files
232        .iter()
233        .filter(|file| file.change_type != ChangeType::Deleted && file.content.is_some())
234        .collect();
235
236    if !content_files.is_empty() {
237        let content_section = content_files
238            .iter()
239            .map(|file| {
240                let change_indicator = match file.change_type {
241                    ChangeType::Added => "➕",
242                    ChangeType::Modified => "✏️",
243                    ChangeType::Deleted => "",
244                };
245
246                format!(
247                    "{} File: {}\nFull File Content:\n{}\n\n--- End of File ---",
248                    change_indicator,
249                    file.path,
250                    file.content
251                        .as_ref()
252                        .expect("File content should be present for added/modified files")
253                )
254            })
255            .collect::<Vec<_>>()
256            .join("\n\n---\n\n");
257
258        all_sections.push(format!(
259            "=== FULL FILE CONTENTS ({} files) ===\n\n{}",
260            content_files.len(),
261            content_section
262        ));
263    }
264
265    all_sections.join("\n\n====================\n\n")
266}
267
268fn format_change_type(change_type: &ChangeType) -> &'static str {
269    match change_type {
270        ChangeType::Added => "Added",
271        ChangeType::Modified => "Modified",
272        ChangeType::Deleted => "Deleted",
273    }
274}
275
276pub fn process_commit_message(message: String, use_gitmoji: bool) -> String {
277    if use_gitmoji {
278        apply_gitmoji(&message)
279    } else {
280        message
281    }
282}
283
284/// Creates a system prompt for code review generation
285pub fn create_review_system_prompt(config: &Config) -> anyhow::Result<String> {
286    let review_schema = schemars::schema_for!(GeneratedReview);
287    let review_schema_str = serde_json::to_string_pretty(&review_schema)?;
288
289    let mut prompt = String::from(
290        "You are an AI assistant specializing in code reviews. \
291        Your task is to provide a comprehensive, professional, and constructive review of the code changes provided.
292
293        Work step-by-step and follow these guidelines exactly:
294
295        1. Analyze the code changes carefully, focusing on:
296           - Code quality and readability
297           - Potential bugs or errors
298           - Architecture and design patterns
299           - Performance implications
300           - Security considerations
301           - Maintainability and testability
302
303        2. Provide constructive feedback:
304           - Be specific and actionable in your suggestions
305           - Point out both strengths and areas for improvement
306           - Explain why certain patterns or practices are problematic
307           - Suggest alternative approaches when appropriate
308
309        3. Focus on substantive issues:
310           - Prioritize significant issues over minor stylistic concerns
311           - Consider the context of the codebase and changes
312           - Note potential edge cases or scenarios that might not be handled
313
314        4. Be professional and constructive:
315           - Frame feedback positively and constructively
316           - Focus on the code, not the developer
317           - Acknowledge good practices and improvements
318
319        Your review should be based entirely on the information provided in the context, without any speculation or assumptions.
320      ");
321
322    prompt.push_str(get_combined_instructions(config).as_str());
323
324    prompt.push_str("
325        Your response must be a valid JSON object with the following structure:
326
327        {
328          \"summary\": \"A brief summary of the changes and their quality\",
329          \"code_quality\": \"An assessment of the overall code quality\",
330          \"suggestions\": [\"Suggestion 1\", \"Suggestion 2\", ...],
331          \"issues\": [\"Issue 1\", \"Issue 2\", ...],
332          \"positive_aspects\": [\"Positive aspect 1\", \"Positive aspect 2\", ...]
333        }
334
335        Follow these steps to generate the code review:
336
337        1. Analyze the provided context, including staged changes and project metadata.
338        2. Evaluate the code quality, looking for potential issues, improvements, and good practices.
339        3. Create a concise summary of the changes and their quality.
340        4. List specific issues found in the code.
341        5. Provide actionable suggestions for improvements.
342        6. Acknowledge positive aspects and good practices in the code.
343        7. Construct the final JSON object with all components.
344
345        Here's a minimal example of the expected output format:
346
347        {
348          \"summary\": \"The changes implement a new authentication system with good separation of concerns, but lacks proper error handling in several places.\",
349          \"code_quality\": \"The code is generally well-structured with clear naming conventions. The architecture follows established patterns, but there are some inconsistencies in error handling approaches.\",
350          \"suggestions\": [\"Consider implementing a consistent error handling strategy across all authentication operations\", \"Add unit tests for edge cases in the token validation logic\"],
351          \"issues\": [\"Missing error handling in the user registration flow\", \"Potential race condition in token refresh mechanism\"],
352          \"positive_aspects\": [\"Good separation of concerns with clear service boundaries\", \"Consistent naming conventions throughout the added components\"]
353        }
354
355        Ensure that your response is a valid JSON object matching this structure.
356        "
357    );
358
359    prompt.push_str(&review_schema_str);
360
361    Ok(prompt)
362}
363
364/// Creates a user prompt for code review generation
365pub fn create_review_user_prompt(context: &CommitContext) -> String {
366    let scorer = RelevanceScorer::new();
367    let relevance_scores = scorer.score(context);
368    let detailed_changes = format_detailed_changes(&context.staged_files, &relevance_scores);
369
370    let prompt = format!(
371        "Based on the following context, generate a code review:\n\n\
372        Branch: {}\n\n\
373        Recent commits:\n{}\n\n\
374        Staged changes:\n{}\n\n\
375        Project metadata:\n{}\n\n\
376        Detailed changes:\n{}",
377        context.branch,
378        format_recent_commits(&context.recent_commits),
379        format_staged_files(&context.staged_files, &relevance_scores),
380        format_project_metadata(&context.project_metadata),
381        detailed_changes
382    );
383
384    log_debug!(
385        "Generated review prompt for {} files ({} added, {} modified, {} deleted)",
386        context.staged_files.len(),
387        context
388            .staged_files
389            .iter()
390            .filter(|f| matches!(f.change_type, ChangeType::Added))
391            .count(),
392        context
393            .staged_files
394            .iter()
395            .filter(|f| matches!(f.change_type, ChangeType::Modified))
396            .count(),
397        context
398            .staged_files
399            .iter()
400            .filter(|f| matches!(f.change_type, ChangeType::Deleted))
401            .count()
402    );
403
404    prompt
405}