git_iris/changes/
prompt.rs

1use super::{
2    change_analyzer::AnalyzedChange,
3    models::{ChangeMetrics, ChangelogResponse, ReleaseNotesResponse},
4};
5use crate::common::{get_combined_instructions, DetailLevel};
6use crate::config::Config;
7use crate::gitmoji::get_gitmoji_list;
8
9#[allow(clippy::unwrap_used)] // todo: handle unwrap
10pub fn create_changelog_system_prompt(config: &Config) -> String {
11    let changelog_schema = schemars::schema_for!(ChangelogResponse);
12    let changelog_schema_str = serde_json::to_string_pretty(&changelog_schema).unwrap();
13
14    let mut prompt = String::from(
15        "You are an AI assistant specialized in generating clear, concise, and informative changelogs for software projects. \
16        Your task is to create a well-structured changelog based on the provided commit information and analysis. \
17        The changelog should adhere to the Keep a Changelog 1.1.0 format (https://keepachangelog.com/en/1.1.0/).
18
19        Work step-by-step and follow these guidelines exactly:
20
21        1. Categorize changes into the following types: Added, Changed, Deprecated, Removed, Fixed, Security.
22        2. Use present tense and imperative mood in change descriptions.
23        3. Start each change entry with a capital letter and do not end with a period.
24        4. Be concise but descriptive in change entries and ensure good grammar, capitalization, and punctuation.
25        5. Include *short* commit hashes at the end of each entry.
26        6. Focus on the impact and significance of the changes and omit trivial changes below the relevance threshold.
27        7. Find commonalities and group related changes together under the appropriate category.
28        8. List the most impactful changes first within each category.
29        9. Mention associated issue numbers and pull request numbers when available.
30        10. Clearly identify and explain any breaking changes.
31        11. Avoid common cliché words (like 'enhance', 'streamline', 'leverage', etc) and phrases.
32        12. Do not speculate about the purpose of a change or add any information not directly supported by the context.
33        13. Mention any changes to dependencies or build configurations under the appropriate category.
34        14. Highlight changes that affect multiple parts of the codebase or have cross-cutting concerns.
35        15. NO YAPPING!
36
37        Your response must be a valid JSON object with the following structure:
38
39        {
40          \"version\": \"string or null\",
41          \"release_date\": \"string or null\",
42          \"sections\": {
43            \"Added\": [{ \"description\": \"string\", \"commit_hashes\": [\"string\"], \"associated_issues\": [\"string\"], \"pull_request\": \"string or null\" }],
44            \"Changed\": [...],
45            \"Deprecated\": [...],
46            \"Removed\": [...],
47            \"Fixed\": [...],
48            \"Security\": [...]
49          },
50          \"breaking_changes\": [{ \"description\": \"string\", \"commit_hash\": \"string\" }],
51          \"metrics\": {
52            \"total_commits\": number,
53            \"files_changed\": number,
54            \"insertions\": number,
55            \"deletions\": number
56          }
57        }
58
59        Follow these steps to generate the changelog:
60
61        1. Analyze the provided commit information and group changes by type (Added, Changed, etc.).
62        2. For each change type, create an array of change entries with description, commit hashes, associated issues, and pull request (if available).
63        3. Identify any breaking changes and add them to the breaking_changes array.
64        4. Calculate the metrics based on the overall changes.
65        5. If provided, include the version and release date.
66        6. Construct the final JSON object ensuring all required fields are present.
67
68        Here's a minimal example of the expected output format:
69
70        {
71          \"version\": \"1.0.0\",
72          \"release_date\": \"2023-08-15\",
73          \"sections\": {
74            \"Added\": [
75              {
76                \"description\": \"add new feature X\",
77                \"commit_hashes\": [\"abc123\"],
78              }
79            ],
80            \"Changed\": [],
81            \"Deprecated\": [],
82            \"Removed\": [],
83            \"Fixed\": [],
84            \"Security\": []
85          },
86          \"breaking_changes\": [],
87          \"metrics\": {
88            \"total_commits\": 1,
89            \"files_changed\": 3,
90            \"insertions\": 100,
91            \"deletions\": 50
92          }
93        }
94
95        Ensure that your response is a valid JSON object matching this structure. Include all required fields, even if they are empty arrays or null values.
96        "
97    );
98
99    prompt.push_str(&changelog_schema_str);
100
101    prompt.push_str(get_combined_instructions(config).as_str());
102
103    if config.use_gitmoji {
104        prompt.push_str(
105            "\n\nWhen generating the changelog, include tasteful, appropriate, and intelligent use of emojis to add visual interest.\n \
106            Here are some examples of emojis you can use:\n");
107        prompt.push_str(&get_gitmoji_list());
108    }
109
110    prompt.push_str(
111        "\n\nYou will be provided with detailed information about each change, including file-level analysis, impact scores, and classifications. \
112        Use this information to create a comprehensive and insightful changelog. \
113        Adjust the level of detail based on the specified detail level (Minimal, Standard, or Detailed)."
114    );
115
116    prompt
117}
118
119#[allow(clippy::unwrap_used)] // todo: handle unwrap maybe use try_from instead
120pub fn create_release_notes_system_prompt(config: &Config) -> String {
121    let release_notes_schema = schemars::schema_for!(ReleaseNotesResponse);
122    let release_notes_schema_str = serde_json::to_string_pretty(&release_notes_schema).unwrap();
123
124    let mut prompt = String::from(
125        "You are an AI assistant specialized in generating comprehensive and user-friendly release notes for software projects. \
126        Your task is to create detailed release notes based on the provided commit information and analysis. \
127        Aim for a tone that is professional, approachable, and authoritative, keeping in mind any additional user instructions.
128
129        Work step-by-step and follow these guidelines exactly:
130
131        1. Provide a high-level summary of the release, highlighting key features, improvements, and fixes.
132        2. Find commonalities and group changes into meaningful sections (e.g., 'New Features', 'Improvements', 'Bug Fixes', 'Breaking Changes').
133        3. Focus on the impact and benefits of the changes to users and developers.
134        4. Highlight any significant new features or major improvements.
135        5. Explain the rationale behind important changes when possible.
136        6. Note any breaking changes and provide clear upgrade instructions.
137        7. Mention any changes to dependencies or system requirements.
138        8. Include any relevant documentation updates or new resources for users.
139        9. Use clear, non-technical language where possible to make the notes accessible to a wide audience.
140        10. Provide context for technical changes when necessary.
141        11. Highlight any security updates or important bug fixes.
142        12. Include overall metrics to give context about the scope of the release.
143        13. Mention associated issue numbers and pull request numbers when relevant.
144        14. NO YAPPING!
145
146        Your response must be a valid JSON object with the following structure:
147
148        {
149          \"version\": \"string or null\",
150          \"release_date\": \"string or null\",
151          \"sections\": {
152            \"Added\": [{ \"description\": \"string\", \"commit_hashes\": [\"string\"], \"associated_issues\": [\"string\"], \"pull_request\": \"string or null\" }],
153            \"Changed\": [...],
154            \"Deprecated\": [...],
155            \"Removed\": [...],
156            \"Fixed\": [...],
157            \"Security\": [...]
158          },
159          \"breaking_changes\": [{ \"description\": \"string\", \"commit_hash\": \"string\" }],
160          \"metrics\": {
161            \"total_commits\": number,
162            \"files_changed\": number,
163            \"insertions\": number,
164            \"deletions\": number
165            \"total_lines_changed\": number
166          }
167        }
168
169        Follow these steps to generate the changelog:
170
171        1. Analyze the provided commit information and group changes by type (Added, Changed, etc.).
172        2. For each change type, create an array of change entries with description, commit hashes, associated issues, and pull request (if available).
173        3. Identify any breaking changes and add them to the breaking_changes array.
174        4. Calculate the metrics based on the overall changes.
175        5. If provided, include the version and release date.
176        6. Construct the final JSON object ensuring all required fields are present.
177
178        Here's a minimal example of the expected output format:
179
180        {
181          \"version\": \"1.0.0\",
182          \"release_date\": \"2023-08-15\",
183          \"sections\": {
184            \"Added\": [
185              {
186                \"description\": \"add new feature X\",
187                \"commit_hashes\": [\"abc123\"],
188                \"associated_issues\": [\"#42\"],
189                \"pull_request\": \"PR #100\"
190              }
191            ],
192            \"Changed\": [],
193            \"Deprecated\": [],
194            \"Removed\": [],
195            \"Fixed\": [],
196            \"Security\": []
197          },
198          \"breaking_changes\": [],
199          \"metrics\": {
200            \"total_commits\": 1,
201            \"files_changed\": 3,
202            \"insertions\": 100,
203            \"deletions\": 50
204            \"total_lines_changed\": 150
205          }
206        }
207
208        Ensure that your response is a valid JSON object matching this structure. Include all required fields, even if they are empty arrays or null values.
209        "
210    );
211
212    prompt.push_str(&release_notes_schema_str);
213
214    prompt.push_str(get_combined_instructions(config).as_str());
215
216    if config.use_gitmoji {
217        prompt.push_str(
218            "\n\nWhen generating the release notes, include tasteful, appropriate, and intelligent use of emojis to add visual interest.\n \
219            Here are some examples of emojis you can use:\n");
220        prompt.push_str(&get_gitmoji_list());
221    }
222
223    prompt
224}
225
226pub fn create_changelog_user_prompt(
227    changes: &[AnalyzedChange],
228    total_metrics: &ChangeMetrics,
229    detail_level: DetailLevel,
230    from: &str,
231    to: &str,
232    readme_summary: Option<&str>,
233) -> String {
234    let mut prompt =
235        format!("Based on the following changes from {from} to {to}, generate a changelog:\n\n");
236
237    prompt.push_str("Overall Changes:\n");
238    prompt.push_str(&format!("Total commits: {}\n", total_metrics.total_commits));
239    prompt.push_str(&format!("Files changed: {}\n", total_metrics.files_changed));
240    prompt.push_str(&format!(
241        "Total lines changed: {}\n",
242        total_metrics.total_lines_changed
243    ));
244    prompt.push_str(&format!("Insertions: {}\n", total_metrics.insertions));
245    prompt.push_str(&format!("Deletions: {}\n\n", total_metrics.deletions));
246
247    for change in changes {
248        prompt.push_str(&format!("Commit: {}\n", change.commit_hash));
249        prompt.push_str(&format!("Author: {}\n", change.author));
250        prompt.push_str(&format!("Message: {}\n", change.commit_message));
251        prompt.push_str(&format!("Type: {:?}\n", change.change_type));
252        prompt.push_str(&format!("Breaking Change: {}\n", change.is_breaking_change));
253        prompt.push_str(&format!(
254            "Associated Issues: {}\n",
255            change.associated_issues.join(", ")
256        ));
257        if let Some(pr) = &change.pull_request {
258            prompt.push_str(&format!("Pull Request: {pr}\n"));
259        }
260        prompt.push_str(&format!(
261            "Files changed: {}\n",
262            change.metrics.files_changed
263        ));
264        prompt.push_str(&format!(
265            "Lines changed: {}\n",
266            change.metrics.total_lines_changed
267        ));
268        prompt.push_str(&format!("Insertions: {}\n", change.metrics.insertions));
269        prompt.push_str(&format!("Deletions: {}\n", change.metrics.deletions));
270        prompt.push_str(&format!("Impact score: {:.2}\n", change.impact_score));
271
272        match detail_level {
273            DetailLevel::Minimal => {
274                // For minimal detail, we don't include file-level changes
275            }
276            DetailLevel::Standard | DetailLevel::Detailed => {
277                prompt.push_str("File changes:\n");
278                for file_change in &change.file_changes {
279                    prompt.push_str(&format!(
280                        "  - {} ({:?})\n",
281                        file_change.new_path, file_change.change_type
282                    ));
283                    if detail_level == DetailLevel::Detailed {
284                        for analysis in &file_change.analysis {
285                            prompt.push_str(&format!("    * {analysis}\n"));
286                        }
287                    }
288                }
289            }
290        }
291
292        prompt.push('\n');
293    }
294
295    if let Some(summary) = readme_summary {
296        prompt.push_str("\nProject README Summary:\n");
297        prompt.push_str(summary);
298        prompt.push_str("\n\n");
299    }
300
301    prompt.push_str(&format!("Please generate a {} changelog for the changes from {} to {}, adhering to the Keep a Changelog format. ", 
302        match detail_level {
303            DetailLevel::Minimal => "concise",
304            DetailLevel::Standard => "comprehensive",
305            DetailLevel::Detailed => "highly detailed",
306        },
307        from,
308        to
309    ));
310
311    prompt.push_str("Categorize the changes appropriately and focus on the most significant updates and their impact on the project. ");
312    prompt.push_str("For each change, provide a clear description of what was changed, adhering to the guidelines in the system prompt. ");
313    prompt.push_str("Include the commit hashes, associated issues, and pull request numbers for each entry when available. ");
314    prompt.push_str("Clearly identify and explain any breaking changes. ");
315
316    if readme_summary.is_some() {
317        prompt.push_str("Use the README summary to provide context about the project and ensure the changelog reflects the project's goals and main features. ");
318    }
319
320    prompt
321}
322
323pub fn create_release_notes_user_prompt(
324    changes: &[AnalyzedChange],
325    total_metrics: &ChangeMetrics,
326    detail_level: DetailLevel,
327    from: &str,
328    to: &str,
329    readme_summary: Option<&str>,
330) -> String {
331    let mut prompt =
332        format!("Based on the following changes from {from} to {to}, generate release notes:\n\n");
333
334    prompt.push_str("Overall Changes:\n");
335    prompt.push_str(&format!("Total commits: {}\n", changes.len()));
336    prompt.push_str(&format!("Files changed: {}\n", total_metrics.files_changed));
337    prompt.push_str(&format!(
338        "Total lines changed: {}\n",
339        total_metrics.total_lines_changed
340    ));
341    prompt.push_str(&format!("Insertions: {}\n", total_metrics.insertions));
342    prompt.push_str(&format!("Deletions: {}\n\n", total_metrics.deletions));
343
344    for change in changes {
345        prompt.push_str(&format!("Commit: {}\n", change.commit_hash));
346        prompt.push_str(&format!("Author: {}\n", change.author));
347        prompt.push_str(&format!("Message: {}\n", change.commit_message));
348        prompt.push_str(&format!("Type: {:?}\n", change.change_type));
349        prompt.push_str(&format!("Breaking Change: {}\n", change.is_breaking_change));
350        prompt.push_str(&format!(
351            "Associated Issues: {}\n",
352            change.associated_issues.join(", ")
353        ));
354        if let Some(pr) = &change.pull_request {
355            prompt.push_str(&format!("Pull Request: {pr}\n"));
356        }
357        prompt.push_str(&format!("Impact score: {:.2}\n", change.impact_score));
358
359        match detail_level {
360            DetailLevel::Minimal => {
361                // For minimal detail, we don't include file-level changes
362            }
363            DetailLevel::Standard | DetailLevel::Detailed => {
364                prompt.push_str("File changes:\n");
365                for file_change in &change.file_changes {
366                    prompt.push_str(&format!(
367                        "  - {} ({:?})\n",
368                        file_change.new_path, file_change.change_type
369                    ));
370                    if detail_level == DetailLevel::Detailed {
371                        for analysis in &file_change.analysis {
372                            prompt.push_str(&format!("    * {analysis}\n"));
373                        }
374                    }
375                }
376            }
377        }
378
379        prompt.push('\n');
380    }
381
382    if let Some(summary) = readme_summary {
383        prompt.push_str("\nProject README Summary:\n");
384        prompt.push_str(summary);
385        prompt.push_str("\n\n");
386    }
387
388    prompt.push_str(&format!(
389        "Please generate {} release notes for the changes from {} to {}. ",
390        match detail_level {
391            DetailLevel::Minimal => "concise",
392            DetailLevel::Standard => "comprehensive",
393            DetailLevel::Detailed => "highly detailed",
394        },
395        from,
396        to
397    ));
398
399    prompt.push_str("Focus on the impact and benefits of the changes to users and developers. ");
400    prompt.push_str("Highlight key features, improvements, and fixes. ");
401    prompt.push_str("Include a high-level summary of the release, major changes, and any breaking changes or important upgrade notes. ");
402    prompt.push_str("Group changes into meaningful sections and explain the rationale behind important changes when possible. ");
403    prompt.push_str("Include associated issue numbers and pull request numbers when relevant. ");
404
405    match detail_level {
406        DetailLevel::Minimal => {
407            prompt.push_str(
408                "Keep the release notes brief and focused on the most significant changes. ",
409            );
410        }
411        DetailLevel::Standard => {
412            prompt.push_str("Provide a balanced overview of all important changes, with some details on major features or fixes. ");
413        }
414        DetailLevel::Detailed => {
415            prompt.push_str("Include detailed explanations of changes, their rationale, and potential impact on the project or workflow. ");
416            prompt.push_str("Provide context for technical changes and include file-level details where relevant. ");
417        }
418    }
419
420    if readme_summary.is_some() {
421        prompt.push_str("Ensure the release notes align with the project's overall goals and main features as described in the README summary. ");
422    }
423
424    prompt.push_str(
425        "Incorporate the overall metrics to give context about the scope of this release. ",
426    );
427    prompt.push_str("Pay special attention to changes with high impact scores, as they are likely to be the most significant. ");
428
429    prompt
430}