diff --git a/action.yml b/action.yml index f49eeeb..26b187e 100644 --- a/action.yml +++ b/action.yml @@ -24,7 +24,7 @@ inputs: required: false default: "claude/" branch_name_template: - description: "Template for branch naming. Available variables: {{prefix}}, {{entityType}}, {{entityNumber}}, {{timestamp}}, {{year}}, {{month}}, {{day}}, {{hour}}, {{minute}}, {{sha}}, {{label}}. The {{label}} variable uses the first label from the issue/PR, falling back to {{entityType}} if no labels exist. Default: '{{prefix}}{{entityType}}-{{entityNumber}}-{{timestamp}}'" + description: "Template for branch naming. Available variables: {{prefix}}, {{entityType}}, {{entityNumber}}, {{timestamp}}, {{year}}, {{month}}, {{day}}, {{hour}}, {{minute}}, {{sha}}, {{label}}, {{description}}. The {{label}} variable uses the first label from the issue/PR, falling back to {{entityType}} if no labels exist. The {{description}} variable uses the first 3 words of the issue/PR title converted to kebab-case. Default: '{{prefix}}{{entityType}}-{{entityNumber}}-{{timestamp}}'" required: false default: "" allowed_bots: diff --git a/src/github/operations/branch.ts b/src/github/operations/branch.ts index 9f428c8..fc3f5b7 100644 --- a/src/github/operations/branch.ts +++ b/src/github/operations/branch.ts @@ -113,6 +113,9 @@ export async function setupBranch( // Extract first label from GitHub data const firstLabel = extractFirstLabel(githubData); + // Extract title from GitHub data + const title = githubData.contextData.title; + // Generate branch name using template or default format const newBranch = generateBranchName( branchNameTemplate, @@ -121,6 +124,7 @@ export async function setupBranch( entityNumber, sourceSHA, firstLabel, + title, ); // For commit signing, defer branch creation to the file ops server diff --git a/src/utils/branch-template.ts b/src/utils/branch-template.ts index 956dbd1..5839c92 100644 --- a/src/utils/branch-template.ts +++ b/src/utils/branch-template.ts @@ -4,6 +4,25 @@ * Branch name template parsing and variable substitution utilities */ +/** + * Extracts the first three words from a title and converts them to kebab-case + */ +function extractDescription(title: string): string { + if (!title || title.trim() === "") { + return ""; + } + + return title + .trim() // Remove leading/trailing whitespace + .split(/\s+/) // Split on whitespace + .slice(0, 3) // Take first 3 words + .join("-") // Join with hyphens + .toLowerCase() // Convert to lowercase + .replace(/[^a-z0-9-]/g, "") // Remove non-alphanumeric except hyphens + .replace(/-+/g, "-") // Replace multiple hyphens with single + .replace(/^-|-$/g, ""); // Remove leading/trailing hyphens +} + export interface BranchTemplateVariables { prefix: string; entityType: string; @@ -16,6 +35,7 @@ export interface BranchTemplateVariables { minute: string; sha?: string; label?: string; + description?: string; } /** @@ -48,6 +68,7 @@ export function createBranchTemplateVariables( entityNumber: number, sha?: string, label?: string, + title?: string, ): BranchTemplateVariables { const now = new Date(); @@ -63,6 +84,7 @@ export function createBranchTemplateVariables( minute: String(now.getMinutes()).padStart(2, "0"), sha: sha?.substring(0, 8), // First 8 characters of SHA label: label || entityType, // Fall back to entityType if no label + description: title !== undefined ? extractDescription(title) : undefined, }; } @@ -76,6 +98,7 @@ export function generateBranchName( entityNumber: number, sha?: string, label?: string, + title?: string, ): string { const variables = createBranchTemplateVariables( branchPrefix, @@ -83,6 +106,7 @@ export function generateBranchName( entityNumber, sha, label, + title, ); let branchName: string; diff --git a/test/branch-template.test.ts b/test/branch-template.test.ts index 762f06f..ce8a966 100644 --- a/test/branch-template.test.ts +++ b/test/branch-template.test.ts @@ -131,6 +131,83 @@ describe("branch template utilities", () => { ); expect(result.label).toBe("issue"); }); + + it("should extract description from title when provided", () => { + const result = createBranchTemplateVariables( + "test/", + "issue", + 123, + undefined, + undefined, + "Fix login bug with OAuth", + ); + expect(result.description).toBe("fix-login-bug"); + }); + + it("should handle title with special characters", () => { + const result = createBranchTemplateVariables( + "test/", + "issue", + 456, + undefined, + undefined, + "Add: User Registration & Email Validation", + ); + expect(result.description).toBe("add-user-registration"); + }); + + it("should handle title with fewer than 3 words", () => { + const result = createBranchTemplateVariables( + "test/", + "issue", + 789, + undefined, + undefined, + "Bug fix", + ); + expect(result.description).toBe("bug-fix"); + }); + + it("should handle single word title", () => { + const result = createBranchTemplateVariables( + "test/", + "issue", + 101, + undefined, + undefined, + "Refactoring", + ); + expect(result.description).toBe("refactoring"); + }); + + it("should handle empty title", () => { + const result = createBranchTemplateVariables( + "test/", + "issue", + 202, + undefined, + undefined, + "", + ); + expect(result.description).toBe(""); + }); + + it("should handle title with extra whitespace", () => { + const result = createBranchTemplateVariables( + "test/", + "issue", + 303, + undefined, + undefined, + " Update README documentation ", + ); + expect(result.description).toBe("update-readme-documentation"); + }); + + it("should not set description when title is not provided", () => { + const result = createBranchTemplateVariables("test/", "issue", 404); + expect(result.description).toBeUndefined(); + }); }); describe("generateBranchName", () => { @@ -216,5 +293,66 @@ describe("branch template utilities", () => { expect(result).toBe("dev/enhancement-issue_789"); }); + + it("should use description in template when provided", () => { + const template = "{{prefix}}{{description}}/{{entityNumber}}"; + const result = generateBranchName( + template, + "feature/", + "issue", + 123, + undefined, + undefined, + "Fix login bug with OAuth", + ); + + expect(result).toBe("feature/fix-login-bug/123"); + }); + + it("should handle template with multiple variables including description", () => { + const template = + "{{prefix}}{{label}}/{{description}}-{{entityType}}_{{entityNumber}}"; + const result = generateBranchName( + template, + "dev/", + "issue", + 456, + undefined, + "bug", + "User authentication fails completely", + ); + + expect(result).toBe("dev/bug/user-authentication-fails-issue_456"); + }); + + it("should handle description with special characters in template", () => { + const template = "{{prefix}}{{description}}-{{entityNumber}}"; + const result = generateBranchName( + template, + "fix/", + "pr", + 789, + undefined, + undefined, + "Add: User Registration & Email Validation", + ); + + expect(result).toBe("fix/add-user-registration-789"); + }); + + it("should handle empty description in template", () => { + const template = "{{prefix}}{{description}}-{{entityNumber}}"; + const result = generateBranchName( + template, + "test/", + "issue", + 101, + undefined, + undefined, + "", + ); + + expect(result).toBe("test/-101"); + }); }); });