gitai/features/commit/
prompt.rs

1use super::review::{GeneratedReview, QualityDimension};
2use super::types::GeneratedMessage;
3use crate::common::get_combined_instructions;
4use crate::config::Config;
5use crate::core::context::{ChangeType, CommitContext, ProjectMetadata, RecentCommit, StagedFile};
6
7use super::relevance::RelevanceScorer;
8use crate::debug;
9use std::collections::HashMap;
10use std::fmt::Write;
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
19        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 80 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
34        indicated in the context.
35        12. Do not include a conclusion or end summary section.
36        13. Avoid common cliché words (like 'enhance', 'streamline', 'leverage', etc) and phrases.
37        14. Don't mention filenames in the subject line unless absolutely necessary.
38        15. Only describe changes that are explicitly shown in the provided context.
39        16. If the purpose or impact of a change is not clear from the context, focus on describing
40        the change itself without inferring intent.
41        17. Do not use phrases like 'seems to', 'appears to', or 'might be' - only state what is
42        certain based on the context.
43        18. If there's not enough information to create a complete, authoritative message, state only
44        what can be confidently determined from the context.
45        19. NO YAPPING!
46
47        Be sure to quote newlines and any other control characters in your response.
48
49        The message should be based entirely on the information provided in the context,
50        without any speculation or assumptions.
51      ");
52
53    prompt.push_str(get_combined_instructions(config).as_str());
54
55    prompt.push_str("
56        Your response must be a valid JSON object with the following structure:
57
58        {
59          \"emoji\": \"string or null\",
60          \"title\": \"string\",
61          \"message\": \"string\"
62        }
63
64        Follow these steps to generate the commit message:
65
66        1. Analyze the provided context, including staged changes, recent commits, and project metadata.
67        2. Identify the main purpose of the commit based on the changes.
68        3. Create a concise and descriptive title (subject line) for the commit.
69        4. If using emojis (false unless stated below), select the most appropriate one for the commit type.
70        5. Write a detailed message body explaining the changes, their impact, and any other relevant information.
71        6. Ensure the message adheres to the guidelines above, and follows all of the additional instructions provided.
72        7. Construct the final JSON object with the emoji (if applicable), title, and message.
73
74         Here's a minimal example of the expected output format:
75
76        {
77          \"emoji\": \"✨\",
78          \"title\": \"Add user authentication feature\",
79          \"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\"
80        }
81
82        Ensure that your response is a valid JSON object matching this structure. Include an empty string for the emoji if not using one.
83        "
84    );
85
86    prompt.push_str(&commit_schema_str);
87
88    Ok(prompt)
89}
90
91pub fn create_user_prompt(context: &CommitContext) -> String {
92    let scorer = RelevanceScorer::new();
93    let relevance_scores = scorer.score(context);
94    let detailed_changes = format_detailed_changes(&context.staged_files, &relevance_scores);
95
96    let prompt = format!(
97        "Based on the following context, generate a Git commit message:\n\n\
98        Branch: {}\n\n\
99        Recent commits:\n{}\n\n\
100        Staged changes:\n{}\n\n\
101        Project metadata:\n{}\n\n\
102        Detailed changes:\n{}",
103        context.branch,
104        format_recent_commits(&context.recent_commits),
105        format_staged_files(&context.staged_files, &relevance_scores),
106        format_project_metadata(&context.project_metadata),
107        detailed_changes
108    );
109
110    debug!(
111        "Generated commit prompt for {} files ({} added, {} modified, {} deleted)",
112        context.staged_files.len(),
113        context
114            .staged_files
115            .iter()
116            .filter(|f| matches!(f.change_type, ChangeType::Added))
117            .count(),
118        context
119            .staged_files
120            .iter()
121            .filter(|f| matches!(f.change_type, ChangeType::Modified))
122            .count(),
123        context
124            .staged_files
125            .iter()
126            .filter(|f| matches!(f.change_type, ChangeType::Deleted))
127            .count()
128    );
129
130    prompt
131}
132
133fn format_recent_commits(commits: &[RecentCommit]) -> String {
134    commits
135        .iter()
136        .map(|commit| format!("{} - {}", &commit.hash[..7], commit.message))
137        .collect::<Vec<_>>()
138        .join("\n")
139}
140
141fn format_staged_files(files: &[StagedFile], relevance_scores: &HashMap<String, f32>) -> String {
142    files
143        .iter()
144        .map(|file| {
145            let relevance = relevance_scores.get(&file.path).unwrap_or(&0.0);
146            format!(
147                "{} ({:.2}) - {}",
148                file.path,
149                relevance,
150                format_change_type(&file.change_type)
151            )
152        })
153        .collect::<Vec<_>>()
154        .join("\n")
155}
156
157fn format_project_metadata(metadata: &ProjectMetadata) -> String {
158    format!(
159        "Language: {}\nFramework: {}\nDependencies: {}",
160        metadata.language.as_deref().unwrap_or("None"),
161        metadata.framework.as_deref().unwrap_or("None"),
162        metadata.dependencies.join(", ")
163    )
164}
165
166fn format_detailed_changes(
167    files: &[StagedFile],
168    relevance_scores: &HashMap<String, f32>,
169) -> String {
170    let mut all_sections = Vec::new();
171
172    // Add a statistical summary at the top
173    let added_count = files
174        .iter()
175        .filter(|f| matches!(f.change_type, ChangeType::Added))
176        .count();
177    let modified_count = files
178        .iter()
179        .filter(|f| matches!(f.change_type, ChangeType::Modified))
180        .count();
181    let deleted_count = files
182        .iter()
183        .filter(|f| matches!(f.change_type, ChangeType::Deleted))
184        .count();
185
186    let summary = format!(
187        "CHANGE SUMMARY:\n- {} file(s) added\n- {} file(s) modified\n- {} file(s) deleted\n- {} total file(s) changed",
188        added_count,
189        modified_count,
190        deleted_count,
191        files.len()
192    );
193    all_sections.push(summary);
194
195    // First section: File summaries with diffs
196    let diff_section = files
197        .iter()
198        .map(|file| {
199            let relevance = relevance_scores.get(&file.path).unwrap_or(&0.0);
200
201            format!(
202                "File: {} (Relevance: {:.2})\nChange Type: {}\nAnalysis:\n{}\n\nDiff:\n{}",
203                file.path,
204                relevance,
205                format_change_type(&file.change_type),
206                file.analysis.join("\n"),
207                file.diff
208            )
209        })
210        .collect::<Vec<_>>()
211        .join("\n\n---\n\n");
212
213    all_sections.push(format!(
214        "=== DIFFS ({} files) ===\n\n{}",
215        files.len(),
216        diff_section
217    ));
218
219    // Second section: Full file contents (only for added/modified files)
220    let content_files: Vec<_> = files
221        .iter()
222        .filter(|file| file.change_type != ChangeType::Deleted && file.content.is_some())
223        .collect();
224
225    if !content_files.is_empty() {
226        let content_section = content_files
227            .iter()
228            .map(|file| {
229                let change_indicator = match file.change_type {
230                    ChangeType::Added | ChangeType::Deleted => "",
231                    ChangeType::Modified => "✏️",
232                };
233
234                format!(
235                    "{} File: {}\nFull File Content:\n{}\n\n--- End of File ---",
236                    change_indicator,
237                    file.path,
238                    file.content
239                        .as_ref()
240                        .expect("File content should be present for added/modified files")
241                )
242            })
243            .collect::<Vec<_>>()
244            .join("\n\n---\n\n");
245
246        all_sections.push(format!(
247            "=== FULL FILE CONTENTS ({} files) ===\n\n{}",
248            content_files.len(),
249            content_section
250        ));
251    }
252
253    all_sections.join("\n\n====================\n\n")
254}
255
256fn format_change_type(change_type: &ChangeType) -> &'static str {
257    match change_type {
258        ChangeType::Added => "Added",
259        ChangeType::Modified => "Modified",
260        ChangeType::Deleted => "Deleted",
261    }
262}
263
264/// Creates a system prompt for code review generation
265#[allow(clippy::too_many_lines)]
266pub fn create_review_system_prompt(config: &Config) -> anyhow::Result<String> {
267    let review_schema = schemars::schema_for!(GeneratedReview);
268    let review_schema_str = serde_json::to_string_pretty(&review_schema)?;
269
270    let mut prompt = String::from(
271        "You are an AI assistant specializing in code reviews.
272        Your task is to provide a comprehensive, professional, and constructive review of the code
273        changes provided. Work step-by-step and follow these guidelines exactly:
274
275        1. Analyze the code changes carefully, focusing on:
276           - Code quality and readability
277           - Potential bugs or errors
278           - Architecture and design patterns
279           - Performance implications
280           - Security considerations
281           - Maintainability and testability
282
283        2. Provide constructive feedback:
284           - Be specific and actionable in your suggestions
285           - Point out both strengths and areas for improvement
286           - Explain why certain patterns or practices are problematic
287           - Suggest alternative approaches when appropriate
288
289        3. Focus on substantive issues:
290           - Prioritize significant issues over minor stylistic concerns
291           - Consider the context of the codebase and changes
292           - Note potential edge cases or scenarios that might not be handled
293
294        4. Be professional and constructive:
295           - Frame feedback positively and constructively
296           - Focus on the code, not the developer
297           - Acknowledge good practices and improvements
298
299        5. Analyze the following specific dimensions of code quality:
300        ",
301    );
302
303    // Add each dimension's description
304    for dimension in QualityDimension::all() {
305        prompt.push_str(dimension.description());
306    }
307
308    prompt.push_str(
309        "
310        For each dimension, identify specific issues with:
311        - A severity level (Critical, High, Medium, Low)
312        - Line number references or specific location in the code
313        - Explanation of why it's problematic
314        - Concrete recommendation for improvement
315
316        Your review should be based entirely on the information provided in the context, without any speculation or assumptions.
317      ");
318
319    prompt.push_str(get_combined_instructions(config).as_str());
320
321    prompt.push_str(
322        "
323        Your response must be a valid JSON object with the following structure:
324
325        {
326          \"summary\": \"A brief summary of the changes and their quality\",
327          \"code_quality\": \"An assessment of the overall code quality\",
328          \"suggestions\": [\"Suggestion 1\", \"Suggestion 2\", ...],
329          \"issues\": [\"Issue 1\", \"Issue 2\", ...],
330          \"positive_aspects\": [\"Positive aspect 1\", \"Positive aspect 2\", ...],",
331    );
332
333    // Add each dimension to the JSON schema
334    let mut is_first = true;
335    for dimension in QualityDimension::all() {
336        let dim_name = match dimension {
337            QualityDimension::Complexity => "complexity",
338            QualityDimension::Abstraction => "abstraction",
339            QualityDimension::Deletion => "deletion",
340            QualityDimension::Hallucination => "hallucination",
341            QualityDimension::Style => "style",
342            QualityDimension::Security => "security",
343            QualityDimension::Performance => "performance",
344            QualityDimension::Duplication => "duplication",
345            QualityDimension::ErrorHandling => "error_handling",
346            QualityDimension::Testing => "testing",
347            QualityDimension::BestPractices => "best_practices",
348        };
349
350        if is_first {
351            is_first = false;
352            write!(
353                &mut prompt,
354                "
355          \"{dim_name}\": {{
356            \"issues_found\": true/false,
357            \"issues\": [
358              {{
359                \"description\": \"Brief description\",
360                \"severity\": \"Critical/High/Medium/Low\",
361                \"location\": \"filename.rs:line_numbers or path/to/file.rs:lines_range\",
362                \"explanation\": \"Detailed explanation of the issue\",
363                \"recommendation\": \"Specific suggestion for improvement\"
364              }},
365              ...
366            ]
367          }}"
368            )
369            .expect("write to string should not fail");
370        } else {
371            write!(
372                &mut prompt,
373                ",
374          \"{dim_name}\": {{ ... similar structure ... }}"
375            )
376            .expect("write to string should not fail");
377        }
378    }
379
380    prompt.push_str("
381        }
382
383        Follow these steps to generate the code review:
384
385        1. Analyze the provided context, including staged changes and project metadata.
386        2. Evaluate the code quality, looking for potential issues, improvements, and good practices.
387        3. Create a concise summary of the changes and their quality.
388        4. Analyze each of the code quality dimensions.
389        5. For each dimension with issues, list them with appropriate severity, location, explanation, and recommendation.
390        6. Provide overall suggestions for improvements.
391        7. Identify specific issues found across all dimensions.
392        8. Acknowledge positive aspects and good practices in the code.
393        9. Construct the final JSON object with all components.
394
395        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.
396
397        Here's a minimal example of the expected output format (showing only two dimensions for brevity):
398
399        {
400          \"summary\": \"The changes implement a new authentication system with good separation of concerns, but lacks proper error handling in several places.\",
401          \"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.\",
402          \"suggestions\": [\"Consider implementing a consistent error handling strategy across all authentication operations\", \"Add unit tests for edge cases in the token validation logic\"],
403          \"issues\": [\"Missing error handling in the user registration flow\", \"Potential race condition in token refresh mechanism\"],
404          \"positive_aspects\": [\"Good separation of concerns with clear service boundaries\", \"Consistent naming conventions throughout the added components\"],
405          \"complexity\": {
406            \"issues_found\": true,
407            \"issues\": [
408              {
409                \"description\": \"Complex authentication flow with excessive nesting\",
410                \"severity\": \"Medium\",
411                \"location\": \"src/auth/auth_service.rs:45-67\",
412                \"explanation\": \"The authentication validation contains 5 levels of nesting, making it difficult to follow the logic flow.\",
413                \"recommendation\": \"Extract validation steps into separate functions and use early returns to reduce nesting\"
414              }
415            ]
416          },
417          \"error_handling\": {
418            \"issues_found\": true,
419            \"issues\": [
420              {
421                \"description\": \"Missing error handling in token refresh\",
422                \"severity\": \"High\",
423                \"location\": \"src/auth/auth_service.rs:102-120\",
424                \"explanation\": \"The token refresh function doesn't properly handle network timeouts, potentially leaving users in an inconsistent state.\",
425                \"recommendation\": \"Add explicit error handling for network timeouts with appropriate user feedback\"
426              }
427            ]
428          },
429          ... (other dimensions would be included with empty issues arrays if no issues found)
430        }
431
432        Ensure that your response is a valid JSON object matching this structure.
433        "
434    );
435
436    prompt.push_str(&review_schema_str);
437
438    Ok(prompt)
439}
440
441/// Creates a user prompt for code review generation
442pub fn create_review_user_prompt(context: &CommitContext) -> String {
443    let scorer = RelevanceScorer::new();
444    let relevance_scores = scorer.score(context);
445    let detailed_changes = format_detailed_changes(&context.staged_files, &relevance_scores);
446
447    let prompt = format!(
448        "Based on the following context, generate a code review:\n\n\
449        Branch: {}\n\n\
450        Recent commits:\n{}\n\n\
451        Staged changes:\n{}\n\n\
452        Project metadata:\n{}\n\n\
453        Detailed changes:\n{}",
454        context.branch,
455        format_recent_commits(&context.recent_commits),
456        format_staged_files(&context.staged_files, &relevance_scores),
457        format_project_metadata(&context.project_metadata),
458        detailed_changes
459    );
460
461    debug!(
462        "Generated review prompt for {} files ({} added, {} modified, {} deleted)",
463        context.staged_files.len(),
464        context
465            .staged_files
466            .iter()
467            .filter(|f| matches!(f.change_type, ChangeType::Added))
468            .count(),
469        context
470            .staged_files
471            .iter()
472            .filter(|f| matches!(f.change_type, ChangeType::Modified))
473            .count(),
474        context
475            .staged_files
476            .iter()
477            .filter(|f| matches!(f.change_type, ChangeType::Deleted))
478            .count()
479    );
480
481    prompt
482}
483
484/// Creates a system prompt for PR description generation
485pub fn create_pr_system_prompt(config: &Config) -> anyhow::Result<String> {
486    let pr_schema = schemars::schema_for!(super::types::GeneratedPullRequest);
487    let pr_schema_str = serde_json::to_string_pretty(&pr_schema)?;
488
489    let mut prompt = String::from(
490        "You are an AI assistant specializing in generating comprehensive, professional pull request descriptions. \
491        Your task is to create clear, informative, and well-structured PR descriptions based on the provided context.
492
493        Work step-by-step and follow these guidelines exactly:
494
495        1. Analyze the commits and changes to understand the overall purpose of the PR
496        2. Create a concise, descriptive title that summarizes the main changes
497        3. Write a brief summary that captures the essence of what was changed
498        4. Provide a detailed description explaining what was changed, why it was changed, and how it works
499        5. List all commits included in this PR for reference
500        6. Identify any breaking changes and explain their impact
501        7. Provide testing instructions if the changes require specific testing steps
502        8. Include any additional notes or context that would be helpful for reviewers
503
504        Guidelines for PR descriptions:
505        - Focus on the overall impact and purpose, not individual commit details
506        - Explain the 'why' behind changes, not just the 'what'
507        - Use clear, professional language suitable for code review
508        - Organize information logically with proper sections
509        - Be comprehensive but concise
510        - Consider the audience: other developers who need to review and understand the changes
511        - Highlight any configuration changes, migrations, or deployment considerations
512        - Mention any dependencies or prerequisites
513        - Note any performance implications or architectural decisions
514
515        Your description should treat the changeset as an atomic unit representing a cohesive feature,
516        fix, or improvement, rather than a collection of individual commits.
517        ");
518
519    prompt.push_str(get_combined_instructions(config).as_str());
520
521    prompt.push_str(
522        "
523        Your response must be a valid JSON object with the following structure:
524
525        {
526          \"emoji\": \"string or null\",
527          \"title\": \"Clear, descriptive PR title\",
528          \"summary\": \"Brief overview of the changes\",
529          \"description\": \"Detailed explanation organized into Features section with sub-sections for Core Capabilities, Technical Details, CLI/Integration details, etc.\",
530          \"commits\": [\"List of commit messages included in this PR\"],
531          \"breaking_changes\": [\"Any breaking changes introduced\"],
532          \"testing_notes\": \"Instructions for testing these changes (optional)\",
533          \"notes\": \"Additional context or notes for reviewers (optional)\"
534        }
535
536        Follow these steps to generate the PR description:
537
538        1. Analyze the provided context, including commit messages, file changes, and project metadata
539        2. Identify the main theme or purpose that unifies all the changes
540        3. Create a clear, descriptive title that captures the essence of the PR
541        4. If using emojis, select the most appropriate one for the PR type
542        5. Write a concise summary highlighting the key changes and their impact
543        6. Organize the description into a Features section with logical sub-sections
544        7. List all commit messages for reference and traceability
545        8. Identify any breaking changes and explain their impact on users or systems
546        9. Provide testing instructions if the changes require specific testing procedures
547        10. Add any additional notes about deployment, configuration, or other considerations
548        11. Construct the final JSON object with all components
549
550        Example output format:
551
552        {
553          \"emoji\": \"✨\",
554          \"title\": \"Add comprehensive Experience Fragment management system\",
555          \"summary\": \"Implements full lifecycle support for Experience Fragments (XFs), including create, retrieve, update, and page integration operations. Adds a unified agent tool, rich CLI interface, and tight AEM manager integration with tenant-specific configuration support.\",
556          \"description\": \"### Core Capabilities\\n\\n* Unified `manage_experience_fragments` tool with four key operations:\\n  * `create`: Create new XFs with optional initial content\\n  * `get`: Retrieve existing XF data\\n  * `update`: Modify XF content\\n  * `add_to_page`: Inject XF references into pages with flexible positioning\\n\\n* AEM manager integration with `createExperienceFragment` and `populateExperienceFragment`\\n* Support for tenant-specific `experienceFragmentComponentType` configuration\\n\\n###  Technical Details\\n\\n* Secure CSRF token handling for all operations\\n* XF page structure conversion for accurate population\\n* AEM 6.5 vs AEM Cloud component type detection\\n* Unique XF name generation with randomized suffixes\\n* Comprehensive validation and error handling\\n* State change logging for operational observability\\n\\n### 🖥 CLI Tooling\\n\\n* New command-line script with full XF management\\n* Commands: `create`, `update`, `list`, `get`, `delete`, `search`, `info`\\n* Content file input/output support\\n* XF discovery and metadata analysis tools\",
557          \"commits\": [\"b1b1f3f: feat(xf): add experience fragment management system\"],
558          \"breaking_changes\": [],
559          \"testing_notes\": \"Verified XF creation, update, and population. Confirmed CLI command behavior across all operations. Tested page integration and position logic. Checked tenant-specific component resolution.\",
560          \"notes\": \"Tenants using non-default XF components must define `experienceFragmentComponentType`. Requires sufficient AEM permissions and CSRF support.\"
561        }
562
563        Ensure that your response is a valid JSON object matching this structure. Include an empty string for the emoji if not using one.
564        ");
565
566    prompt.push_str(&pr_schema_str);
567
568    Ok(prompt)
569}
570
571/// Creates a user prompt for PR description generation
572pub fn create_pr_user_prompt(context: &CommitContext, commit_messages: &[String]) -> String {
573    let scorer = RelevanceScorer::new();
574    let relevance_scores = scorer.score(context);
575    let detailed_changes = format_detailed_changes(&context.staged_files, &relevance_scores);
576
577    let commits_section = if commit_messages.is_empty() {
578        "No commits available".to_string()
579    } else {
580        commit_messages.join("\n")
581    };
582
583    let prompt = format!(
584        "Based on the following context, generate a comprehensive pull request description:\n\n\
585        Range: {}\n\n\
586        Commits in this PR:\n{}\n\n\
587        Recent commit history:\n{}\n\n\
588        File changes summary:\n{}\n\n\
589        Project metadata:\n{}\n\n\
590        Detailed changes:\n{}",
591        context.branch,
592        commits_section,
593        format_recent_commits(&context.recent_commits),
594        format_staged_files(&context.staged_files, &relevance_scores),
595        format_project_metadata(&context.project_metadata),
596        detailed_changes
597    );
598
599    debug!(
600        "Generated PR prompt for {} files across {} commits",
601        context.staged_files.len(),
602        commit_messages.len()
603    );
604
605    prompt
606}