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. Loads model configuration 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 configured AI provider. .PARAMETER BaseBranch Base branch for git comparison (default: origin/master). Auto-detected in PR builds. .PARAMETER ConfigPath Path to model-config.json file. Defaults to module's embedded config, but can be overridden with customer-specific configuration in .devops/code-review/model-config.json .PARAMETER RulesPath Path to directory containing review rules (*.md files). Defaults to module's embedded rules, but customer can override with .devops/code-review/rules/ .PARAMETER Provider Optional provider override (azure, anthropic, openai, github). If not specified, uses default from config. .PARAMETER MaxTokens Maximum tokens for AI response. If not specified, uses value from provider config. .PARAMETER SeverityFailBuild Severity level that should cause build failure (error, warning, none). Default: error .EXAMPLE Invoke-AICodeReview Uses default configuration to review changed files against origin/master .EXAMPLE Invoke-AICodeReview -Provider "anthropic" -SeverityFailBuild "warning" Use Claude with stricter failure threshold .EXAMPLE Invoke-AICodeReview -ConfigPath ".devops/code-review/model-config.json" -RulesPath ".devops/code-review/rules" Use customer-specific configuration and rules .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]$ConfigPath, [Parameter(Mandatory = $false)] [string]$RulesPath, [Parameter(Mandatory = $false)] [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 configuration path (customer override or module default) if (-not $ConfigPath) { $customerConfig = ".devops/code-review/model-config.json" if (Test-Path $customerConfig) { $ConfigPath = $customerConfig Write-Verbose "Using customer config: $ConfigPath" } else { $ConfigPath = Join-Path $PSScriptRoot "../../Rules/model-config.json" Write-Verbose "Using module default config: $ConfigPath" } } # 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 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)" } # Step 4: Load model configuration Write-Host "`nStep 4: Loading model configuration..." $config = Get-ModelConfig -ConfigPath $ConfigPath # Determine provider $providerName = if ($Provider) { $Provider } else { $config.default.provider } $providerConfig = $config.providers.$providerName if (-not $providerConfig) { throw "Provider not found in configuration: $providerName" } Write-Host "✅ Using provider: $providerName ($($providerConfig.type))" # 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 = if ($providerConfig.max_tokens) { $providerConfig.max_tokens } else { 4000 } } # 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 } "azure" { Invoke-AzureAIReview -ReviewContext $reviewContext -Rules $rules -ApiKey $apiKey -Config $providerConfig -MaxTokens $MaxTokens } "openai" { Invoke-OpenAIReview -ReviewContext $reviewContext -Rules $rules -ApiKey $apiKey -Config $providerConfig -MaxTokens $MaxTokens } "github" { Invoke-GitHubModelsReview -ReviewContext $reviewContext -Rules $rules -ApiKey $apiKey -Config $providerConfig -MaxTokens $MaxTokens } 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)" } } |