Public/Get-AvmUpdateRisk.ps1

function Get-AvmUpdateRisk {
    <#
    .SYNOPSIS
        Assesses the breaking-change risk of upgrading a single AVM module from current to
        target version.

    .DESCRIPTION
        Gathers three independent signals to classify risk:
        1. Version semantics (major bump = HIGH; 0.x minor = potentially breaking; patch = LOW).
        2. Interface diff — compares inputs/outputs between current and target version using
           module metadata or source files. Breaking changes (required input added, input removed/
           renamed, type changed, output removed/renamed) raise risk to HIGH.
        3. Changelog evidence — extracts breaking-change mentions from CHANGELOG.md or GitHub
           Releases between the two versions.
        Returns a risk tier (HIGH | MEDIUM | LOW | UNKNOWN) plus concrete riskReasons[].

    .PARAMETER Reference
        The AVM module reference object from Get-AvmModuleReference.

    .PARAMETER TargetVersion
        The version to upgrade to.

    .OUTPUTS
        PSCustomObject with: riskTier, riskReasons[], interfaceDiff, changelogLines[].

    .EXAMPLE
        $ref = Get-AvmModuleReference -Path ./infra | Select-Object -First 1
        Get-AvmUpdateRisk -Reference $ref -TargetVersion '0.5.0'
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$Reference,

        [Parameter(Mandatory)]
        [string]$TargetVersion
    )

    $riskReasons     = [System.Collections.Generic.List[string]]::new()
    $updateType      = Get-AvmUpdateType -Current $Reference.currentVersion -Target $TargetVersion

    # --- Signal 1: Version semantics ---
    switch ($updateType) {
        'major' {
            $riskReasons.Add("Major version bump ($($Reference.currentVersion) → $TargetVersion) — may contain breaking changes by semver contract.")
        }
        'minor' {
            if ($Reference.currentVersion -match '^0\.') {
                $riskReasons.Add("0.x minor bump ($($Reference.currentVersion) → $TargetVersion) — AVM modules in 0.x treat minor as potentially breaking.")
            }
        }
        'patch' { <# patch is expected non-breaking by semver #> }
    }

    # --- Signal 2: Interface diff ---
    $diff = Get-AvmInterfaceDiff -Reference $Reference -TargetVersion $TargetVersion

    $interfaceBreaking = $false
    if ($diff.available) {
        foreach ($p in $diff.addedRequired)  { $riskReasons.Add("BREAKING: Required input added with no default: '$p'"); $interfaceBreaking = $true }
        foreach ($p in $diff.removedInputs)  { $riskReasons.Add("BREAKING: Input removed/renamed: '$p'"); $interfaceBreaking = $true }
        foreach ($p in $diff.typeChanged)    { $riskReasons.Add("BREAKING: Type changed: $p"); $interfaceBreaking = $true }
        foreach ($o in $diff.removedOutputs) { $riskReasons.Add("BREAKING: Output removed/renamed: '$o'"); $interfaceBreaking = $true }
        foreach ($p in $diff.addedOptional)  { $riskReasons.Add("Non-breaking: New optional input added: '$p'") }
        foreach ($o in $diff.addedOutputs)   { $riskReasons.Add("Non-breaking: New output added: '$o'") }
    }

    # --- Signal 3: Changelog ---
    $changelog = Get-AvmChangelog -Reference $Reference -TargetVersion $TargetVersion
    $changelogBreaking = $false
    if ($changelog.available -and $changelog.lines.Count -gt 0) {
        $changelogBreaking = $true
        foreach ($l in $changelog.lines) { $riskReasons.Add("Changelog: $l") }
    }

    # --- Determine risk tier (UNKNOWN check BEFORE patch-LOW) ---
    $riskTier = if ($updateType -eq 'major' -or $interfaceBreaking -or $changelogBreaking) {
        'HIGH'
    } elseif (-not $diff.available -and -not $changelog.available) {
        $riskReasons.Add("UNKNOWN: Interface diff and changelog unavailable — cannot confirm safety.")
        'UNKNOWN'
    } elseif ($updateType -eq 'minor' -and ($Reference.currentVersion -match '^0\.')) {
        'MEDIUM'
    } elseif ($updateType -eq 'patch' -and -not $interfaceBreaking) {
        'LOW'
    } else {
        # diff available, no breaking changes, minor on 1.x+ or other cases
        'MEDIUM'
    }

    return [PSCustomObject]@{
        riskTier       = $riskTier
        riskReasons    = $riskReasons.ToArray()
        interfaceDiff  = $diff
        changelogLines = if ($changelog.available) { $changelog.lines } else { @() }
    }
}