Skip to content

Commit df9320c

Browse files
committed
✨ Add emoji support to PR description generation
Implement emoji support for pull request descriptions to match commit message functionality - Add emoji field to GeneratedPullRequest type - Update PR system prompt to include gitmoji usage guidelines - Modify PR service methods to respect use_gitmoji configuration - Update PR formatting to display emoji in title when present - Remove word wrapping from PR sections for better web UI display - Fix textwrap import to use module directly instead of wrap function - Update test cases to include emoji field in mock data PR descriptions now support the same emoji functionality as commit messages, with proper configuration handling and formatting.
1 parent 73e8491 commit df9320c

File tree

6 files changed

+89
-46
lines changed

6 files changed

+89
-46
lines changed

src/commit/cli.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,8 @@ fn setup_pr_service(
196196
common,
197197
repository_url,
198198
config,
199-
false, // gitmoji not needed for PR descriptions
200-
false, // verification not needed for PR descriptions
199+
config.use_gitmoji, // Use gitmoji setting from config for PR descriptions
200+
false, // verification not needed for PR descriptions
201201
)
202202
}
203203

src/commit/prompt.rs

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -547,14 +547,43 @@ pub fn create_pr_system_prompt(config: &Config) -> anyhow::Result<String> {
547547

548548
prompt.push_str(get_combined_instructions(config).as_str());
549549

550+
if config.use_gitmoji {
551+
prompt.push_str(
552+
"\n\nUse emojis strategically to create visual structure and reinforce section meaning. \
553+
Use a single gitmoji at the start of the PR title, and include emojis in section headers \
554+
to create a clean, professional, and visually structured PR description. \
555+
Choose relevant emojis from the following list:\n\n",
556+
);
557+
prompt.push_str(&get_gitmoji_list());
558+
prompt.push_str(
559+
"\n\nEmoji placement guidelines:\
560+
\n- Use emojis in ALL major section headers (## Summary, ## Features, ## Testing, etc.)\
561+
\n- Include emojis in key sub-section headers within Features for visual hierarchy\
562+
\n- Use ⚠️ specifically for breaking changes\
563+
\n- Keep the actual content clean and professional without scattered emojis\
564+
\n- Choose emojis that reinforce the section's purpose and meaning\
565+
\n- Maintain consistency in emoji selection across similar sections\
566+
\n\nRecommended section emojis:\
567+
\n- 🧩 Summary (puzzle piece for overview)\
568+
\n- 📦 Features (package for new functionality)\
569+
\n- 🚀 Core Capabilities (rocket for main features)\
570+
\n- 🛠 Technical Details (wrench for implementation)\
571+
\n- 🧪 Testing (test tube for testing info)\
572+
\n- 📝 Notes (memo for additional context)\
573+
\n- 🔍 Commits (magnifying glass for commit list)\
574+
\n- ⚠️ Breaking Changes (warning for breaking changes)\n\n",
575+
);
576+
}
577+
550578
prompt.push_str(
551579
"
552580
Your response must be a valid JSON object with the following structure:
553581
554582
{
583+
\"emoji\": \"string or null\",
555584
\"title\": \"Clear, descriptive PR title\",
556585
\"summary\": \"Brief overview of the changes\",
557-
\"description\": \"Detailed explanation of what was changed and why\",
586+
\"description\": \"Detailed explanation organized into Features section with sub-sections for Core Capabilities, Technical Details, CLI/Integration details, etc.\",
558587
\"commits\": [\"List of commit messages included in this PR\"],
559588
\"breaking_changes\": [\"Any breaking changes introduced\"],
560589
\"testing_notes\": \"Instructions for testing these changes (optional)\",
@@ -566,27 +595,29 @@ pub fn create_pr_system_prompt(config: &Config) -> anyhow::Result<String> {
566595
1. Analyze the provided context, including commit messages, file changes, and project metadata
567596
2. Identify the main theme or purpose that unifies all the changes
568597
3. Create a clear, descriptive title that captures the essence of the PR
569-
4. Write a concise summary highlighting the key changes and their impact
570-
5. Provide a detailed description explaining the changes, their rationale, and implementation approach
571-
6. List all commit messages for reference and traceability
572-
7. Identify any breaking changes and explain their impact on users or systems
573-
8. Provide testing instructions if the changes require specific testing procedures
574-
9. Add any additional notes about deployment, configuration, or other considerations
575-
10. Construct the final JSON object with all components
598+
4. If using emojis, select the most appropriate one for the PR type
599+
5. Write a concise summary highlighting the key changes and their impact
600+
6. Organize the description into a Features section with logical sub-sections
601+
7. List all commit messages for reference and traceability
602+
8. Identify any breaking changes and explain their impact on users or systems
603+
9. Provide testing instructions if the changes require specific testing procedures
604+
10. Add any additional notes about deployment, configuration, or other considerations
605+
11. Construct the final JSON object with all components
576606
577607
Example output format:
578608
579609
{
580-
\"title\": \"Add user authentication with JWT tokens and role-based access control\",
581-
\"summary\": \"Implements a comprehensive authentication system with JWT tokens, user registration/login, and role-based permissions. Includes middleware for route protection and database migrations for user management.\",
582-
\"description\": \"This PR introduces a complete authentication system to secure the application:\\n\\n**Features Added:**\\n- JWT-based authentication with access and refresh tokens\\n- User registration and login endpoints\\n- Role-based access control (admin, user roles)\\n- Password hashing with bcrypt\\n- Authentication middleware for protected routes\\n\\n**Technical Details:**\\n- Uses industry-standard JWT libraries for token management\\n- Implements secure password storage with salt rounds\\n- Adds database migrations for user and role tables\\n- Includes comprehensive error handling and validation\\n\\n**Security Considerations:**\\n- Tokens expire after 24 hours with refresh mechanism\\n- Passwords are hashed with bcrypt (12 rounds)\\n- CORS configuration updated for authentication headers\",
583-
\"commits\": [\"abc1234: Add JWT authentication middleware\", \"def5678: Implement user registration endpoint\", \"ghi9012: Add login functionality with password validation\"],
584-
\"breaking_changes\": [\"All API endpoints now require authentication headers\", \"Database schema changes require migration\"],
585-
\"testing_notes\": \"Test user registration and login flows. Verify that protected routes reject unauthenticated requests. Check token refresh mechanism with expired tokens.\",
586-
\"notes\": \"Requires environment variables: JWT_SECRET, JWT_EXPIRES_IN. Run database migrations before deployment.\"
610+
\"emoji\": \"\",
611+
\"title\": \"Add comprehensive Experience Fragment management system\",
612+
\"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.\",
613+
\"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\",
614+
\"commits\": [\"b1b1f3f: feat(xf): add experience fragment management system\"],
615+
\"breaking_changes\": [],
616+
\"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.\",
617+
\"notes\": \"Tenants using non-default XF components must define `experienceFragmentComponentType`. Requires sufficient AEM permissions and CSRF support.\"
587618
}
588619
589-
Ensure that your response is a valid JSON object matching this structure.
620+
Ensure that your response is a valid JSON object matching this structure. Include an empty string for the emoji if not using one.
590621
");
591622

592623
prompt.push_str(&pr_schema_str);

src/commit/service.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -599,13 +599,20 @@ impl IrisCommitService {
599599
super::prompt::create_pr_user_prompt(ctx, &commit_messages)
600600
});
601601

602-
llm::get_message::<super::types::GeneratedPullRequest>(
602+
let mut generated_pr = llm::get_message::<super::types::GeneratedPullRequest>(
603603
&config_clone,
604604
&self.provider_name,
605605
&system_prompt,
606606
&final_user_prompt,
607607
)
608-
.await
608+
.await?;
609+
610+
// Apply gitmoji setting
611+
if !self.use_gitmoji {
612+
generated_pr.emoji = None;
613+
}
614+
615+
Ok(generated_pr)
609616
}
610617

611618
/// Generate a PR description for branch comparison
@@ -668,13 +675,20 @@ impl IrisCommitService {
668675
super::prompt::create_pr_user_prompt(ctx, &commit_messages)
669676
});
670677

671-
llm::get_message::<super::types::GeneratedPullRequest>(
678+
let mut generated_pr = llm::get_message::<super::types::GeneratedPullRequest>(
672679
&config_clone,
673680
&self.provider_name,
674681
&system_prompt,
675682
&final_user_prompt,
676683
)
677-
.await
684+
.await?;
685+
686+
// Apply gitmoji setting
687+
if !self.use_gitmoji {
688+
generated_pr.emoji = None;
689+
}
690+
691+
Ok(generated_pr)
678692
}
679693

680694
/// Performs a commit with the given message.

src/commit/types.rs

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use schemars::JsonSchema;
22
use serde::{Deserialize, Serialize};
33
use std::fmt::Write;
4-
use textwrap::wrap;
4+
use textwrap;
55

66
/// Model for commit message generation results
77
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
@@ -17,6 +17,8 @@ pub struct GeneratedMessage {
1717
/// Model for pull request description generation results
1818
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
1919
pub struct GeneratedPullRequest {
20+
/// Optional emoji for the pull request title
21+
pub emoji: Option<String>,
2022
/// Pull request title
2123
pub title: String,
2224
/// Brief summary of the changes
@@ -44,7 +46,7 @@ pub fn format_commit_message(response: &GeneratedMessage) -> String {
4446
message.push_str(&response.title);
4547
message.push_str("\n\n");
4648

47-
let wrapped_message = wrap(&response.message, 78);
49+
let wrapped_message = textwrap::wrap(&response.message, 78);
4850
for line in wrapped_message {
4951
message.push_str(&line);
5052
message.push('\n');
@@ -57,24 +59,23 @@ pub fn format_commit_message(response: &GeneratedMessage) -> String {
5759
pub fn format_pull_request(response: &GeneratedPullRequest) -> String {
5860
let mut message = String::new();
5961

60-
// Title
61-
writeln!(&mut message, "# {}", response.title).expect("write to string should not fail");
62+
// Title with optional emoji
63+
if let Some(emoji) = &response.emoji {
64+
writeln!(&mut message, "# {emoji} {}", response.title)
65+
.expect("write to string should not fail");
66+
} else {
67+
writeln!(&mut message, "# {}", response.title).expect("write to string should not fail");
68+
}
6269
message.push('\n');
6370

64-
// Summary
71+
// Summary - no word wrapping for web UI display
6572
writeln!(&mut message, "## Summary").expect("write to string should not fail");
66-
let wrapped_summary = wrap(&response.summary, 78);
67-
for line in wrapped_summary {
68-
writeln!(&mut message, "{line}").expect("write to string should not fail");
69-
}
73+
writeln!(&mut message, "{}", response.summary).expect("write to string should not fail");
7074
message.push('\n');
7175

72-
// Description
76+
// Description - no word wrapping for web UI display
7377
writeln!(&mut message, "## Description").expect("write to string should not fail");
74-
let wrapped_description = wrap(&response.description, 78);
75-
for line in wrapped_description {
76-
writeln!(&mut message, "{line}").expect("write to string should not fail");
77-
}
78+
writeln!(&mut message, "{}", response.description).expect("write to string should not fail");
7879
message.push('\n');
7980

8081
// Commits
@@ -95,23 +96,17 @@ pub fn format_pull_request(response: &GeneratedPullRequest) -> String {
9596
message.push('\n');
9697
}
9798

98-
// Testing notes
99+
// Testing notes - no word wrapping for web UI display
99100
if let Some(testing) = &response.testing_notes {
100101
writeln!(&mut message, "## Testing").expect("write to string should not fail");
101-
let wrapped_testing = wrap(testing, 78);
102-
for line in wrapped_testing {
103-
writeln!(&mut message, "{line}").expect("write to string should not fail");
104-
}
102+
writeln!(&mut message, "{testing}").expect("write to string should not fail");
105103
message.push('\n');
106104
}
107105

108-
// Additional notes
106+
// Additional notes - no word wrapping for web UI display
109107
if let Some(notes) = &response.notes {
110108
writeln!(&mut message, "## Notes").expect("write to string should not fail");
111-
let wrapped_notes = wrap(notes, 78);
112-
for line in wrapped_notes {
113-
writeln!(&mut message, "{line}").expect("write to string should not fail");
114-
}
109+
writeln!(&mut message, "{notes}").expect("write to string should not fail");
115110
}
116111

117112
message

tests/pr_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ fn test_format_pull_request() {
108108
#[test]
109109
fn test_format_pull_request_minimal() {
110110
let pr = GeneratedPullRequest {
111+
emoji: None,
111112
title: "Fix bug in user authentication".to_string(),
112113
summary: "Fixes a critical bug in the authentication flow".to_string(),
113114
description: "This PR fixes an issue where users couldn't log in properly.".to_string(),
@@ -265,6 +266,7 @@ fn test_pr_prompt_with_large_commit_list() {
265266
#[test]
266267
fn test_format_pull_request_with_unicode() {
267268
let pr = GeneratedPullRequest {
269+
emoji: None,
268270
title: "Add 🚀 deployment automation".to_string(),
269271
summary: "Implements automated deployment with emojis 🎉".to_string(),
270272
description: "This PR adds deployment automation:\n\n• Feature 1\n• Feature 2 ✅"

tests/test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,7 @@ impl MockDataBuilder {
513513
/// Create a mock `GeneratedPullRequest`
514514
pub fn generated_pull_request() -> GeneratedPullRequest {
515515
GeneratedPullRequest {
516+
emoji: None,
516517
title: "Add JWT authentication with user registration".to_string(),
517518
summary: "Implements comprehensive JWT-based authentication system with user registration, login, and middleware for protected routes.".to_string(),
518519
description: "This PR introduces a complete authentication system:\n\n**Features Added:**\n- JWT token generation and validation\n- User registration endpoint\n- Authentication middleware for protected routes\n- Password hashing with bcrypt\n\n**Technical Details:**\n- Uses industry-standard JWT libraries\n- Implements secure password storage\n- Includes comprehensive error handling".to_string(),

0 commit comments

Comments
 (0)