Public/New-PSUAiPoweredPullRequest.ps1

function New-PSUAiPoweredPullRequest {
    <#
    .SYNOPSIS
        Uses AI assistance to generate a professional Pull Request (PR) title and description from Git change summaries.
 
    .DESCRIPTION
        This function takes Git change summaries and uses Invoke-PSUAiPrompt to produce
        a meaningful PR title and description written from a developer or DevOps perspective.
 
    .PARAMETER BaseBranch
        (Optional) The base branch to compare against.
        Default value is the default branch from git symbolic-ref refs/remotes/origin/HEAD.
 
    .PARAMETER FeatureBranch
        (Optional) The feature branch being merged.
        Default value is the current git branch from git branch --show-current.
 
    .PARAMETER PullRequestTemplate
        (Optional) Path to the Pull Request template file.
        Default value is "C:\Temp\PRTemplate.txt".
 
    .PARAMETER CompleteOnApproval
        (Optional) Switch parameter to enable auto-completion when the pull request is approved.
        This will be passed to the underlying PR creation function.
     
    .PARAMETER PAT
        (Optional) Personal Access Token for Azure DevOps authentication.
        Default value is $env:PAT. Set using: Set-PSUUserEnvironmentVariable -Name "PAT" -Value "your_pat_token"
 
    .OUTPUTS
        [PSCustomObject]
 
    .EXAMPLE
        New-PSUAiPoweredPullRequest
 
    .EXAMPLE
        New-PSUAiPoweredPullRequest -CompleteOnApproval
 
        Generates an AI-powered pull request that will automatically complete when approved.
 
    .NOTES
        Author: Lakshmanachari Panuganti
        Date: 28th July 2025
 
    .LINK
        https://github.com/lakshmanachari-panuganti/OMG.PSUtilities/tree/main/OMG.PSUtilities.AI
        https://www.linkedin.com/in/lakshmanachari-panuganti/
        https://www.powershellgallery.com/packages/OMG.PSUtilities.AI
        https://ai.google.dev/gemini-api/docs
 
    #>


    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSAvoidUsingWriteHost',
        '',
        Justification = 'This is intended for this function to display formatted output to the user on the console'
    )]
    param (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$BaseBranch = $((git symbolic-ref refs/remotes/origin/HEAD) -replace '^refs/remotes/origin/', ''),

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$FeatureBranch = $(git branch --show-current),

        [Parameter()]
        [ValidateScript({
                if ($_ -and -not (Test-Path $_)) {
                    throw "PR template file not found: $_"
                }
                return $true
            })]
        [string]$PullRequestTemplatePath,

        [Parameter()]
        [switch]$CompleteOnApproval,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$PAT = $env:PAT
    )

    # Parameter display
    Write-Host "FeatureBranch: $FeatureBranch" -ForegroundColor Cyan
    Write-Host "BaseBranch: $BaseBranch" -ForegroundColor Cyan
    Write-Host "PullRequestTemplatePath: $PullRequestTemplatePath" -ForegroundColor Cyan
    Write-Host "CompleteOnApproval: $CompleteOnApproval" -ForegroundColor Cyan

    $UpdateChangeLog = Read-Host "Do you want me to update ChangeLog.md file with the changes? (Y/N)"
    if ($UpdateChangeLog -eq 'Y') {
        Update-PSUChangeLog
        Invoke-PSUGitCommit
        Start-Sleep -Seconds 3
    }
    $ChangeSummary = Get-PSUAiPoweredGitChangeSummary
    if (-not $ChangeSummary) {
        Write-Warning "No changes found."
        return
    }

    # Convert summaries into a nice prompt for AI
    $formattedChanges = ($ChangeSummary | ForEach-Object {
        "- File: $($_.File) | Change: $($_.TypeOfChange) | Summary: $($_.Summary)"
    }) -join "`n"

    # Handle PR template content
    $PRTemplateContent = ""
    $PRTemplateStatement = @()
    if ($PullRequestTemplatePath -and (Test-Path $PullRequestTemplatePath)) {
        try {
            $PRTemplateContent = Get-Content -Path $PullRequestTemplatePath -ErrorAction Stop | Out-String
            $PRTemplateStatement = @(
                "NOTE: Please follow the Pull Request template guidelines below carefully:",
                "",
                $PRTemplateContent,
                "",
                "DO NOT modify the template structure.",
                "DO NOT change the checklists or their wording.",
                "Update the checklists by marking the correct [X] where applicable.",
                "DO NOT alter anything that impacts organizational standards.",
                "ONLY update the DESCRIPTION section thoughtfully and clearly."
            )
        } catch {
            Write-Warning "Could not read PR template file: $($_.Exception.Message)"
        } else{
            Write-Verbose "No PR template specified. Proceeding without template."
        }
    }

    # Construct AI prompt
    $prompt = @"
You are a professional software engineer and DevOps expert.
Given the following Git change summaries, generate a concise and professional Pull Request title and a detailed description suitable for code review. The description should be written in a clear human tone, helpful to both developers and reviewers.
 
### Git Change Summaries:
$formattedChanges
 
Remove any repetition or duplicated information in the description.
 
The ONLY valid response format is a single JSON object exactly like this:
 
{
  "title": "<Meaningfull title, that matches the changes>",
  "description": "This pull request introduces the following improvements:\n\n**FileName1 | Updated/New/Deleted** \n*Short description of what changed in this file*\n\n**FileName2 | Updated/New/Deleted** \n*Short description of what changed in this file*\n\n**FileName3 | Updated/New/Deleted** \n*Short description of what changed in this file*\n\n### **Additional Information**\n\n- ** Small Heading (Example Feature/Change 1):** Detailed description of the improvement or change\n- **Small Heading (Example Feature/Change 2):** Detailed description of the improvement or change\n- **Small Heading (Example Feature/Change 3):** Detailed description of the improvement or change\n- **Small Heading (Example Feature/Change 4):** Detailed description of the improvement or change"
}
 
Do not wrap the JSON in markdown or code fences. The response must:
- Start with "{"
- End with "}"
- Contain no text before "{" or after "}"
- Do NOT include phrases like "Here is your response" or "JSON output"
- (Except in inside PR Description)Do NOT include markdown formatting, code fencing, bullet points, or commentary
 
$($PRTemplateStatement -join "`n")
 
If any rule is violated, regenerate the response until it strictly matches the required JSON format.
"@
.Trim()

    # Call AI Assistant with prompt!
    $response = Invoke-PSUAiPrompt -Prompt $prompt -ReturnJsonResponse

    try {
        $parsed = $response | ConvertFrom-Json -ErrorAction Stop

        if (-not $parsed.title -or -not $parsed.description) {
            throw "AI response missing required keys: title and/or description"
        }

        $PRContent = [PSCustomObject]@{
            Title       = $parsed.title
            Description = $parsed.description
        }

        # Copy title + description to clipboard
        "$($parsed.title)`n$($parsed.description)" | Set-Clipboard

        # Generate HTML summary (optional)
        Convert-PSUPullRequestSummaryToHtml -Title $parsed.title -Description $parsed.description -OpenInBrowser

        # Prompt user for action
        do {
            Write-Host 'Choose an option:' -ForegroundColor Yellow
            Write-Host ' Y - Submit the pull request now' -ForegroundColor Cyan
            Write-Host ' N - Cancel and exit' -ForegroundColor Cyan
            Write-Host ' R - Regenerate with new AI content' -ForegroundColor Cyan
            Write-Host ' D - Draft the PR' -ForegroundColor Cyan
            $readHost = Read-Host 'Enter your choice (Y/N/R/D)' -ForegroundColor Yellow
        } while ($readHost -notin 'Y','N','R','D')

        switch ($readHost) {
            'Y' {
                $remoteUrl = (git remote get-url origin).Trim()
                if ($remoteUrl -match 'github\.com') {
                    Write-Host "Creating GitHub pull request..."
                    $params = @{
                        Title       = $PRContent.Title
                        Description = $PRContent.Description
                        Token       = $env:GITHUB_TOKEN
                    }
                    if ($CompleteOnApproval) { $params.CompleteOnApproval = $true }
                    New-PSUGithubPullRequest @params
                } elseif ($remoteUrl -match 'dev\.azure\.com|visualstudio\.com') {
                    Write-Host "Creating Azure DevOps pull request..."
                    $remoteUrl = (git remote get-url origin).Trim()

                    if ($remoteUrl -match '^.+dev\.azure\.com[/:]([^/]+)/([^/]+)/_git/.+$') {
                        $Organization = $matches[1]
                        $projectNameEncoded = $matches[2]
                    } else {
                        throw "Cannot parse project name from $remoteUrl"
                    }
                    $params = @{
                        Title       = $PRContent.Title
                        Description = $PRContent.Description
                        RepositoryName = (git rev-parse --show-toplevel | Split-Path -Leaf)
                        Project        = [uri]::UnescapeDataString($projectNameEncoded)
                        Organization   = $Organization
                        PAT            = $PAT
                    }
                    if ($CompleteOnApproval) { $params.CompleteOnApproval = $true }
                    New-PSUADOPullRequest @params
                } else {
                    Write-Warning "git url: $remoteUrl"
                    Write-Warning "Automatic pull request creation is not supported for this Git provider. Please create the PR manually."
                }
            }
            'N' { Write-Host "Pull request submission canceled." }
            'R' { Write-Host "Regenerating PR content..."; return (New-PSUAiPoweredPullRequest @PSBoundParameters) }
            'D' {
                $remoteUrl = (git remote get-url origin).Trim()
                if ($remoteUrl -match 'github\.com') {
                    Write-Host "Creating draft GitHub pull request..."
                    $params = @{
                        Title       = $PRContent.Title
                        Description = $PRContent.Description
                        Token       = $env:GITHUB_TOKEN
                        Draft       = $true
                    }
                    if ($CompleteOnApproval) { $params.CompleteOnApproval = $true }
                    New-PSUGithubPullRequest @params
                } elseif ($remoteUrl -match 'dev\.azure\.com|visualstudio\.com') {
                    Write-Host "Creating draft Azure DevOps pull request..."
                    $remoteUrl = (git remote get-url origin).Trim()

                    if ($remoteUrl -match '^.+dev\.azure\.com[/:]([^/]+)/([^/]+)/_git/.+$') {
                        $Organization = $matches[1]
                        $projectNameEncoded = $matches[2]
                    } else {
                        throw "Cannot parse project name from $remoteUrl"
                    }

                    $params = @{
                        Title          = $PRContent.Title
                        Description    = $PRContent.Description
                        RepositoryName = (git rev-parse --show-toplevel | Split-Path -Leaf)
                        Project        = [uri]::UnescapeDataString($projectNameEncoded)
                        Organization   = $Organization
                        Draft          = $true
                        PAT            = $PAT
                    }
                    if ($CompleteOnApproval) { $params.CompleteOnApproval = $true }
                    New-PSUADOPullRequest @params
                } else {
                    Write-Warning "git url: $remoteUrl"
                    Write-Warning "Automatic pull request creation is not supported for this Git provider. Please create the PR manually."
                }
            }
        }

    } catch {
        $PSCmdlet.ThrowTerminatingError($_)
    }
}