git_iris/commit/
prompt.rs

1use super::review::{GeneratedReview, QualityDimension};
2use super::types::GeneratedMessage;
3use crate::common::get_combined_instructions;
4use crate::config::Config;
5use crate::context::{ChangeType, CommitContext, ProjectMetadata, RecentCommit, StagedFile};
6use crate::gitmoji::{apply_gitmoji, get_gitmoji_list};
7
8use super::relevance::RelevanceScorer;
9use crate::log_debug;
10use std::collections::HashMap;
11
12pub fn create_system_prompt(config: &Config) -> anyhow::Result<String> {
13    let commit_schema = schemars::schema_for!(GeneratedMessage);
14    let commit_schema_str = serde_json::to_string_pretty(&commit_schema)?;
15
16    let mut prompt = String::from(
17        "You are an AI assistant specializing in creating high-quality, professional Git commit messages. \
18        Your task is to generate clear, concise, and informative commit messages based solely on the provided context.
19        
20        Work step-by-step and follow these guidelines exactly:
21
22        1. Use the imperative mood in the subject line (e.g., 'Add feature' not 'Added feature').
23        2. Limit the subject line to 50 characters if possible, but never exceed 72 characters.
24        3. Capitalize the subject line.
25        4. Do not end the subject line with a period.
26        5. Separate subject from body with a blank line.
27        6. Wrap the body at 72 characters.
28        7. Use the body to explain what changes were made and their impact, and how they were implemented.
29        8. Be specific and avoid vague language.
30        9. Focus on the concrete changes and their effects, not assumptions about intent.
31        10. If the changes are part of a larger feature or fix, state this fact if evident from the context.
32        11. For non-trivial changes, include a brief explanation of the change's purpose if clearly indicated in the context.
33        12. Do not include a conclusion or end summary section.
34        13. Avoid common cliché words (like 'enhance', 'streamline', 'leverage', etc) and phrases.
35        14. Don't mention filenames in the subject line unless absolutely necessary.
36        15. Only describe changes that are explicitly shown in the provided context.
37        16. If the purpose or impact of a change is not clear from the context, focus on describing the change itself without inferring intent.
38        17. Do not use phrases like 'seems to', 'appears to', or 'might be' - only state what is certain based on the context.
39        18. If there's not enough information to create a complete, authoritative message, state only what can be confidently determined from the context.
40        19. NO YAPPING!
41
42        Be sure to quote newlines and any other control characters in your response.
43
44        The message should be based entirely on the information provided in the context,
45        without any speculation or assumptions.
46      ");
47
48    prompt.push_str(get_combined_instructions(config).as_str());
49
50    if config.use_gitmoji {
51        prompt.push_str(
52            "\n\nUse a single gitmoji at the start of the commit message. \
53          Choose the most relevant emoji from the following list:\n\n",
54        );
55        prompt.push_str(&get_gitmoji_list());
56    }
57
58    prompt.push_str("
59        Your response must be a valid JSON object with the following structure:
60
61        {
62          \"emoji\": \"string or null\",
63          \"title\": \"string\",
64          \"message\": \"string\"
65        }
66
67        Follow these steps to generate the commit message:
68
69        1. Analyze the provided context, including staged changes, recent commits, and project metadata.
70        2. Identify the main purpose of the commit based on the changes.
71        3. Create a concise and descriptive title (subject line) for the commit.
72        4. If using emojis (false unless stated below), select the most appropriate one for the commit type.
73        5. Write a detailed message body explaining the changes, their impact, and any other relevant information.
74        6. Ensure the message adheres to the guidelines above, and follows all of the additional instructions provided.
75        7. Construct the final JSON object with the emoji (if applicable), title, and message.
76
77         Here's a minimal example of the expected output format:
78
79        {
80          \"emoji\": \"✨\",
81          \"title\": \"Add user authentication feature\",
82          \"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\"
83        }
84
85        Ensure that your response is a valid JSON object matching this structure. Include an empty string for the emoji if not using one.
86        "
87    );
88
89    prompt.push_str(&commit_schema_str);
90
91    Ok(prompt)
92}
93
94pub fn create_user_prompt(context: &CommitContext) -> String {
95    let scorer = RelevanceScorer::new();
96    let relevance_scores = scorer.score(context);
97    let detailed_changes = format_detailed_changes(&context.staged_files, &relevance_scores);
98
99    let prompt = format!(
100        "Based on the following context, generate a Git commit message:\n\n\
101        Branch: {}\n\n\
102        Recent commits:\n{}\n\n\
103        Staged changes:\n{}\n\n\
104        Project metadata:\n{}\n\n\
105        Detailed changes:\n{}",
106        context.branch,
107        format_recent_commits(&context.recent_commits),
108        format_staged_files(&context.staged_files, &relevance_scores),
109        format_project_metadata(&context.project_metadata),
110        detailed_changes
111    );
112
113    log_debug!(
114        "Generated commit prompt for {} files ({} added, {} modified, {} deleted)",
115        context.staged_files.len(),
116        context
117            .staged_files
118            .iter()
119            .filter(|f| matches!(f.change_type, ChangeType::Added))
120            .count(),
121        context
122            .staged_files
123            .iter()
124            .filter(|f| matches!(f.change_type, ChangeType::Modified))
125            .count(),
126        context
127            .staged_files
128            .iter()
129            .filter(|f| matches!(f.change_type, ChangeType::Deleted))
130            .count()
131    );
132
133    prompt
134}
135
136fn format_recent_commits(commits: &[RecentCommit]) -> String {
137    commits
138        .iter()
139        .map(|commit| format!("{} - {}", &commit.hash[..7], commit.message))
140        .collect::<Vec<_>>()
141        .join("\n")
142}
143
144fn format_staged_files(files: &[StagedFile], relevance_scores: &HashMap<String, f32>) -> String {
145    files
146        .iter()
147        .map(|file| {
148            let relevance = relevance_scores.get(&file.path).unwrap_or(&0.0);
149            format!(
150                "{} ({:.2}) - {}",
151                file.path,
152                relevance,
153                format_change_type(&file.change_type)
154            )
155        })
156        .collect::<Vec<_>>()
157        .join("\n")
158}
159
160fn format_project_metadata(metadata: &ProjectMetadata) -> String {
161    format!(
162        "Language: {}\nFramework: {}\nDependencies: {}",
163        metadata.language.as_deref().unwrap_or("None"),
164        metadata.framework.as_deref().unwrap_or("None"),
165        metadata.dependencies.join(", ")
166    )
167}
168
169fn format_detailed_changes(
170    files: &[StagedFile],
171    relevance_scores: &HashMap<String, f32>,
172) -> String {
173    let mut all_sections = Vec::new();
174
175    // Add a statistical summary at the top
176    let added_count = files
177        .iter()
178        .filter(|f| matches!(f.change_type, ChangeType::Added))
179        .count();
180    let modified_count = files
181        .iter()
182        .filter(|f| matches!(f.change_type, ChangeType::Modified))
183        .count();
184    let deleted_count = files
185        .iter()
186        .filter(|f| matches!(f.change_type, ChangeType::Deleted))
187        .count();
188
189    let summary = format!(
190        "CHANGE SUMMARY:\n- {} file(s) added\n- {} file(s) modified\n- {} file(s) deleted\n- {} total file(s) changed",
191        added_count,
192        modified_count,
193        deleted_count,
194        files.len()
195    );
196    all_sections.push(summary);
197
198    // First section: File summaries with diffs
199    let diff_section = files
200        .iter()
201        .map(|file| {
202            let relevance = relevance_scores.get(&file.path).unwrap_or(&0.0);
203            // Add emoji indicators for change types
204            let change_indicator = match file.change_type {
205                ChangeType::Added => "➕",
206                ChangeType::Modified => "✏️",
207                ChangeType::Deleted => "🗑️",
208            };
209
210            format!(
211                "{} File: {} (Relevance: {:.2})\nChange Type: {}\nAnalysis:\n{}\n\nDiff:\n{}",
212                change_indicator,
213                file.path,
214                relevance,
215                format_change_type(&file.change_type),
216                file.analysis.join("\n"),
217                file.diff
218            )
219        })
220        .collect::<Vec<_>>()
221        .join("\n\n---\n\n");
222
223    all_sections.push(format!(
224        "=== DIFFS ({} files) ===\n\n{}",
225        files.len(),
226        diff_section
227    ));
228
229    // Second section: Full file contents (only for added/modified files)
230    let content_files: Vec<_> = files
231        .iter()
232        .filter(|file| file.change_type != ChangeType::Deleted && file.content.is_some())
233        .collect();
234
235    if !content_files.is_empty() {
236        let content_section = content_files
237            .iter()
238            .map(|file| {
239                let change_indicator = match file.change_type {
240                    ChangeType::Added => "➕",
241                    ChangeType::Modified => "✏️",
242                    ChangeType::Deleted => "",
243                };
244
245                format!(
246                    "{} File: {}\nFull File Content:\n{}\n\n--- End of File ---",
247                    change_indicator,
248                    file.path,
249                    file.content
250                        .as_ref()
251                        .expect("File content should be present for added/modified files")
252                )
253            })
254            .collect::<Vec<_>>()
255            .join("\n\n---\n\n");
256
257        all_sections.push(format!(
258            "=== FULL FILE CONTENTS ({} files) ===\n\n{}",
259            content_files.len(),
260            content_section
261        ));
262    }
263
264    all_sections.join("\n\n====================\n\n")
265}
266
267fn format_change_type(change_type: &ChangeType) -> &'static str {
268    match change_type {
269        ChangeType::Added => "Added",
270        ChangeType::Modified => "Modified",
271        ChangeType::Deleted => "Deleted",
272    }
273}
274
275pub fn process_commit_message(message: String, use_gitmoji: bool) -> String {
276    if use_gitmoji {
277        apply_gitmoji(&message)
278    } else {
279        message
280    }
281}
282
283/// Creates a system prompt for code review generation
284#[allow(clippy::too_many_lines)]
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        5. Analyze the following specific dimensions of code quality:
320        ");
321
322    // Add each dimension's description
323    for dimension in QualityDimension::all() {
324        prompt.push_str(dimension.description());
325    }
326
327    prompt.push_str(
328        "
329        For each dimension, identify specific issues with:
330        - A severity level (Critical, High, Medium, Low)
331        - Line number references or specific location in the code
332        - Explanation of why it's problematic
333        - Concrete recommendation for improvement
334
335        Your review should be based entirely on the information provided in the context, without any speculation or assumptions.
336      ");
337
338    prompt.push_str(get_combined_instructions(config).as_str());
339
340    prompt.push_str(
341        "
342        Your response must be a valid JSON object with the following structure:
343
344        {
345          \"summary\": \"A brief summary of the changes and their quality\",
346          \"code_quality\": \"An assessment of the overall code quality\",
347          \"suggestions\": [\"Suggestion 1\", \"Suggestion 2\", ...],
348          \"issues\": [\"Issue 1\", \"Issue 2\", ...],
349          \"positive_aspects\": [\"Positive aspect 1\", \"Positive aspect 2\", ...],",
350    );
351
352    // Add each dimension to the JSON schema
353    let mut is_first = true;
354    for dimension in QualityDimension::all() {
355        let dim_name = match dimension {
356            QualityDimension::Complexity => "complexity",
357            QualityDimension::Abstraction => "abstraction",
358            QualityDimension::Deletion => "deletion",
359            QualityDimension::Hallucination => "hallucination",
360            QualityDimension::Style => "style",
361            QualityDimension::Security => "security",
362            QualityDimension::Performance => "performance",
363            QualityDimension::Duplication => "duplication",
364            QualityDimension::ErrorHandling => "error_handling",
365            QualityDimension::Testing => "testing",
366            QualityDimension::BestPractices => "best_practices",
367        };
368
369        if is_first {
370            is_first = false;
371            prompt.push_str(&format!(
372                "
373          \"{dim_name}\": {{
374            \"issues_found\": true/false,
375            \"issues\": [
376              {{
377                \"description\": \"Brief description\",
378                \"severity\": \"Critical/High/Medium/Low\",
379                \"location\": \"filename.rs:line_numbers or path/to/file.rs:lines_range\",
380                \"explanation\": \"Detailed explanation of the issue\",
381                \"recommendation\": \"Specific suggestion for improvement\"
382              }},
383              ...
384            ]
385          }}"
386            ));
387        } else {
388            prompt.push_str(&format!(
389                ",
390          \"{dim_name}\": {{ ... similar structure ... }}"
391            ));
392        }
393    }
394
395    prompt.push_str("
396        }
397
398        Follow these steps to generate the code review:
399
400        1. Analyze the provided context, including staged changes and project metadata.
401        2. Evaluate the code quality, looking for potential issues, improvements, and good practices.
402        3. Create a concise summary of the changes and their quality.
403        4. Analyze each of the code quality dimensions.
404        5. For each dimension with issues, list them with appropriate severity, location, explanation, and recommendation.
405        6. Provide overall suggestions for improvements.
406        7. Identify specific issues found across all dimensions.
407        8. Acknowledge positive aspects and good practices in the code.
408        9. Construct the final JSON object with all components.
409
410        Note: It's expected that not all dimensions will have issues. For dimensions without issues, set 'issues_found' to false and provide an empty issues array.
411
412        Here's a minimal example of the expected output format (showing only two dimensions for brevity):
413
414        {
415          \"summary\": \"The changes implement a new authentication system with good separation of concerns, but lacks proper error handling in several places.\",
416          \"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.\",
417          \"suggestions\": [\"Consider implementing a consistent error handling strategy across all authentication operations\", \"Add unit tests for edge cases in the token validation logic\"],
418          \"issues\": [\"Missing error handling in the user registration flow\", \"Potential race condition in token refresh mechanism\"],
419          \"positive_aspects\": [\"Good separation of concerns with clear service boundaries\", \"Consistent naming conventions throughout the added components\"],
420          \"complexity\": {
421            \"issues_found\": true,
422            \"issues\": [
423              {
424                \"description\": \"Complex authentication flow with excessive nesting\",
425                \"severity\": \"Medium\",
426                \"location\": \"src/auth/auth_service.rs:45-67\",
427                \"explanation\": \"The authentication validation contains 5 levels of nesting, making it difficult to follow the logic flow.\",
428                \"recommendation\": \"Extract validation steps into separate functions and use early returns to reduce nesting\"
429              }
430            ]
431          },
432          \"error_handling\": {
433            \"issues_found\": true,
434            \"issues\": [
435              {
436                \"description\": \"Missing error handling in token refresh\",
437                \"severity\": \"High\",
438                \"location\": \"src/auth/auth_service.rs:102-120\",
439                \"explanation\": \"The token refresh function doesn't properly handle network timeouts, potentially leaving users in an inconsistent state.\",
440                \"recommendation\": \"Add explicit error handling for network timeouts with appropriate user feedback\"
441              }
442            ]
443          },
444          ... (other dimensions would be included with empty issues arrays if no issues found)
445        }
446
447        Ensure that your response is a valid JSON object matching this structure.
448        "
449    );
450
451    prompt.push_str(&review_schema_str);
452
453    Ok(prompt)
454}
455
456/// Creates a user prompt for code review generation
457pub fn create_review_user_prompt(context: &CommitContext) -> String {
458    let scorer = RelevanceScorer::new();
459    let relevance_scores = scorer.score(context);
460    let detailed_changes = format_detailed_changes(&context.staged_files, &relevance_scores);
461
462    let prompt = format!(
463        "Based on the following context, generate a code review:\n\n\
464        Branch: {}\n\n\
465        Recent commits:\n{}\n\n\
466        Staged changes:\n{}\n\n\
467        Project metadata:\n{}\n\n\
468        Detailed changes:\n{}",
469        context.branch,
470        format_recent_commits(&context.recent_commits),
471        format_staged_files(&context.staged_files, &relevance_scores),
472        format_project_metadata(&context.project_metadata),
473        detailed_changes
474    );
475
476    log_debug!(
477        "Generated review prompt for {} files ({} added, {} modified, {} deleted)",
478        context.staged_files.len(),
479        context
480            .staged_files
481            .iter()
482            .filter(|f| matches!(f.change_type, ChangeType::Added))
483            .count(),
484        context
485            .staged_files
486            .iter()
487            .filter(|f| matches!(f.change_type, ChangeType::Modified))
488            .count(),
489        context
490            .staged_files
491            .iter()
492            .filter(|f| matches!(f.change_type, ChangeType::Deleted))
493            .count()
494    );
495
496    prompt
497}