Private/Resolve-AvmAzureDevOpsContext.ps1

function Resolve-AvmAzureDevOpsContext {
    <#
    .SYNOPSIS
        Resolves the Azure DevOps organization/project/repository to use for 'az repos'
        commands, failing fast with an actionable error if neither config nor ambient
        defaults can provide them.

    .DESCRIPTION
        Precedence:
        1. Explicit 'azuredevops.organization' / 'project' / 'repository' config keys.
        2. Inside an Azure DevOps pipeline (SYSTEM_TEAMFOUNDATIONCOLLECTIONURI /
           SYSTEM_TEAMPROJECT present), 'az repos' resolves these implicitly — no error.
        3. Locally, 'az devops configure --list' defaults (organization/project only;
           repository can still be inferred by 'az repos pr create' from the git remote).

        Repository is never required — only organization and project must be resolvable.

    .PARAMETER Config
        The 'azuredevops' section of avmupdater.config.json (organization/project/repository).

    .OUTPUTS
        Hashtable with organization/project/repository (any of which may be $null when
        relying on ambient/pipeline defaults).
    #>

    [CmdletBinding()]
    [OutputType([hashtable])]
    param([hashtable]$Config)

    if (-not $Config) { $Config = @{} }

    $result = @{
        organization = $Config.organization
        project      = $Config.project
        repository   = $Config.repository
    }

    # Inside an Azure DevOps pipeline, org/project/repo are supplied implicitly via
    # SYSTEM_* environment variables that the azure-devops CLI extension reads on its own.
    if ($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI -and $env:SYSTEM_TEAMPROJECT) {
        return $result
    }

    if ($result.organization -and $result.project) {
        return $result
    }

    # Running locally — fall back to 'az devops configure --defaults', the last resort.
    if (Get-Command az -ErrorAction SilentlyContinue) {
        $configured = az devops configure --list 2>&1
        if ($LASTEXITCODE -eq 0) {
            $text = $configured -join "`n"
            if (-not $result.organization -and $text -match 'organization\s*=\s*(\S+)') { $result.organization = $Matches[1] }
            if (-not $result.project -and $text -match 'project\s*=\s*(\S+)') { $result.project = $Matches[1] }
        }
    }

    if (-not $result.organization -or -not $result.project) {
        throw "Azure DevOps organization/project could not be resolved. Set 'azuredevops.organization' " + `
              "and 'azuredevops.project' in avmupdater.config.json, or run " + `
              "'az devops configure --defaults organization=<url> project=<name>' before invoking " + `
              "Invoke-AvmUpdate locally. (Inside an Azure DevOps pipeline these are supplied " + `
              "automatically and this check is skipped.)"
    }

    return $result
}