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 }
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 }
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}