Public/Invoke-AICodeReview.ps1
|
function Invoke-AICodeReview { <# .SYNOPSIS Invoke AI-powered code review using configured model provider .DESCRIPTION Main entry point for AI code review. This function orchestrates the entire review process: 1. Configures AI provider (github, azure, openai, anthropic) 2. Gets changed AL files from git 3. Builds review context with diffs 4. Loads review rules 5. Calls appropriate AI provider 6. Parses and returns violations This is a model-agnostic function that works with any supported AI provider. .PARAMETER BaseBranch Base branch for git comparison (default: origin/master). Auto-detected in PR builds. .PARAMETER RulesPath Path to directory containing review rules (*.md files). Customer can provide rules in .devops/code-review/rules/ .PARAMETER Provider AI provider: github, azure, openai, anthropic, or none (to skip review) .PARAMETER MaxTokens Maximum tokens for AI response. If not specified, uses provider default. .PARAMETER SeverityFailBuild Severity level that should cause build failure (error, warning, none). Default: error .EXAMPLE Invoke-AICodeReview -Provider "azure" Review changed files using Azure OpenAI .EXAMPLE Invoke-AICodeReview -Provider "github" -SeverityFailBuild "warning" Use GitHub Models (free) with stricter failure threshold .EXAMPLE Invoke-AICodeReview -Provider "azure" -RulesPath ".devops/code-review/rules" Use customer-specific rules with Azure provider .OUTPUTS System.Object - Review result with Success, Violations, ErrorCount, WarningCount, InfoCount .NOTES Author: waldo Version: 1.0.0 #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [string]$BaseBranch = "origin/master", [Parameter(Mandatory = $false)] [string]$RulesPath, [Parameter(Mandatory = $true)] [ValidateSet('github', 'azure', 'openai', 'anthropic', 'none')] [string]$Provider, [Parameter(Mandatory = $false)] [int]$MaxTokens, [Parameter(Mandatory = $false)] [ValidateSet('error', 'warning', 'none')] [string]$SeverityFailBuild = 'error' ) begin { Write-Verbose "Starting $($MyInvocation.MyCommand.Name)" $ErrorActionPreference = 'Stop' # Load environment variables from .env file if present Import-EnvFile } process { try { # Determine rules path (customer override or module default) if (-not $RulesPath) { $customerRules = ".devops/code-review/rules" if (Test-Path $customerRules) { $RulesPath = $customerRules Write-Verbose "Using customer rules: $RulesPath" } else { $RulesPath = Join-Path $PSScriptRoot "../../Rules" Write-Verbose "Using module default rules: $RulesPath" } } Write-Host "=========================================" Write-Host "AI Code Review - Starting" Write-Host "=========================================" # Step 1: Get changed files Write-Host "`nStep 1: Detecting changed AL files..." $changedFiles = Get-ChangedALFiles -BaseBranch $BaseBranch if ($changedFiles.Count -eq 0) { Write-Host "✅ No AL files changed - skipping review" return @{ Success = $true Violations = @() ErrorCount = 0 WarningCount = 0 InfoCount = 0 Message = "No AL files changed" } } # Step 2: Build review context Write-Host "`nStep 2: Building review context..." $reviewContext = @() foreach ($file in $changedFiles) { $diff = Get-ALFileDiff -FilePath $file -BaseBranch $BaseBranch if ($null -ne $diff) { $reviewContext += $diff } } if ($reviewContext.Count -eq 0) { Write-Host "✅ No substantive changes to review" return @{ Success = $true Violations = @() ErrorCount = 0 WarningCount = 0 InfoCount = 0 Message = "No substantive changes detected" } } Write-Host "✅ Prepared context for $($reviewContext.Count) file(s)" # Step 3: Load review rules Write-Host "`nStep 3: Loading review rules from: $RulesPath" $ruleFiles = Get-ChildItem "$RulesPath/*.md" -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne 'system-prompt.md' } if ($ruleFiles.Count -eq 0) { Write-Warning "No rule files found in: $RulesPath" Write-Warning "Using basic AL best practices" $rules = "Review for AL best practices, code quality, and potential bugs." } else { $rules = $ruleFiles | ForEach-Object { Get-Content $_.FullName -Raw } | Join-String -Separator "`n`n---`n`n" Write-Host "✅ Loaded $($ruleFiles.Count) rule file(s)" # Extract and display guideline titles Write-Host "`nActive Review Guidelines:" foreach ($file in $ruleFiles) { $content = Get-Content $file.FullName -Raw # Extract level 2 headers (## Title) - handle different line endings $lines = $content -split '\r?\n' foreach ($line in $lines) { if ($line -match '^##\s+([^#].*)$') { $title = $matches[1].Trim() Write-Host " • $title" } } } Write-Host "" } # Load custom system prompt if present $systemPromptFile = Join-Path $RulesPath "system-prompt.md" $systemPrompt = $null if (Test-Path $systemPromptFile) { $systemPrompt = Get-Content $systemPromptFile -Raw Write-Host "✅ Loaded custom system prompt" } else { Write-Verbose "No custom system prompt found, using default" } # Step 4: Configure provider Write-Host "`nStep 4: Configuring AI provider..." # Skip AI review if provider is 'none' if ($Provider -eq 'none') { Write-Host "✅ Provider is 'none' - skipping AI review" Write-Host "##[warning]No AI provider configured (provider='none')" Write-Host "##[warning]To enable AI code review, configure an API key in the variable group" return @{ Success = $true Violations = @() ErrorCount = 0 WarningCount = 0 InfoCount = 0 Message = "AI review skipped (no provider configured)" } } # Built-in provider configurations $providerConfigs = @{ 'github' = @{ type = 'openai' api_key_variable = 'GITHUB_TOKEN' endpoint = 'https://models.inference.ai.azure.com/chat/completions' model = 'gpt-4o' max_tokens = 4096 } 'azure' = @{ type = 'azure' api_key_variable = 'AZURE_AI_API_KEY' endpoint = $env:AZURE_AI_ENDPOINT deployment = $env:AZURE_DEPLOYMENT max_tokens = 4096 } 'openai' = @{ type = 'openai' api_key_variable = 'OPENAI_API_KEY' endpoint = 'https://api.openai.com/v1/chat/completions' model = 'gpt-4o' max_tokens = 4096 } 'anthropic' = @{ type = 'anthropic' api_key_variable = 'ANTHROPIC_API_KEY' base_url = 'https://api.anthropic.com/v1' model = 'claude-3-5-sonnet-20241022' max_tokens = 8192 } } $providerConfig = $providerConfigs[$Provider] if (-not $providerConfig) { throw "Unknown provider: $Provider. Supported: github, azure, openai, anthropic" } Write-Host "✅ Using provider: $Provider" # Get API key from environment $apiKey = [Environment]::GetEnvironmentVariable($providerConfig.api_key_variable) if ([string]::IsNullOrWhiteSpace($apiKey)) { throw "API key not found in environment variable: $($providerConfig.api_key_variable)" } # Determine max tokens if (-not $MaxTokens) { $MaxTokens = $providerConfig.max_tokens } # Step 5: Call AI provider Write-Host "`nStep 5: Calling $($providerConfig.type) API..." Write-Host " ⏳ This may take 10-60 seconds..." $violations = switch ($providerConfig.type) { "anthropic" { Invoke-AnthropicReview -ReviewContext $reviewContext -Rules $rules -ApiKey $apiKey -Config $providerConfig -MaxTokens $MaxTokens -SystemPromptText $systemPrompt } "azure" { Invoke-AzureAIReview -ReviewContext $reviewContext -Rules $rules -ApiKey $apiKey -Config $providerConfig -MaxTokens $MaxTokens -SystemPromptText $systemPrompt } "openai" { Invoke-OpenAIReview -ReviewContext $reviewContext -Rules $rules -ApiKey $apiKey -Config $providerConfig -MaxTokens $MaxTokens -SystemPromptText $systemPrompt } "github" { Invoke-GitHubModelsReview -ReviewContext $reviewContext -Rules $rules -ApiKey $apiKey -Config $providerConfig -MaxTokens $MaxTokens -SystemPromptText $systemPrompt } default { throw "Unknown provider type: $($providerConfig.type)" } } # Step 6: Summarize results Write-Host "`nStep 6: Processing results..." $errorCount = ($violations | Where-Object { $_.severity -eq 'error' }).Count $warningCount = ($violations | Where-Object { $_.severity -eq 'warning' }).Count $infoCount = ($violations | Where-Object { $_.severity -eq 'info' }).Count Write-Host "✅ Review complete: $errorCount errors, $warningCount warnings, $infoCount info" # Determine success based on severity threshold $success = switch ($SeverityFailBuild) { 'error' { $errorCount -eq 0 } 'warning' { ($errorCount + $warningCount) -eq 0 } 'none' { $true } } return @{ Success = $success Violations = $violations ErrorCount = $errorCount WarningCount = $warningCount InfoCount = $infoCount SeverityThreshold = $SeverityFailBuild Message = if ($success) { "Code review passed" } else { "Code review found violations" } } } catch { Write-Error "Error in $($MyInvocation.MyCommand.Name): $_" throw } } end { Write-Verbose "Completed $($MyInvocation.MyCommand.Name)" } } |