git_iris/changes/
prompt.rs

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