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)"
    }
}