Public/Get-AvmTool.ps1

function Get-AvmTool {
    <#
    .SYNOPSIS
        List or query managed tools from the tools.lock manifest.

    .DESCRIPTION
        With no -Name, returns one pscustomobject per tool in the lock. With
        one or more -Name values, returns only the matching tool(s).

        Each result has a 'Status' field set per spec section 10 lookup order:
          - 'installed' : the lock-pinned version is in the user cache
                                 and the .verified marker is present.
          - 'installed-on-path': not in the cache, but the entrypoint is on
                                 PATH and self-reports the lock-pinned version.
          - 'outdated-on-path' : on PATH, but the version does not match the
                                 lock.
          - 'missing' : neither cached nor on PATH.

        Routed by the dispatcher:
            avm tool list -> Get-AvmTool
            avm tool which <name> -> Get-AvmTool -Name <name>

    .PARAMETER Name
        One or more tool names (lowercase). Case-sensitive against the lock.

    .PARAMETER LockPath
        Override the bundled Resources/tools.lock.psd1. Intended for tests.

    .PARAMETER NoPathFallback
        Skip the PATH lookup. Useful in unit tests so that the host system's
        PATH cannot influence the result. Production callers leave it off.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Position = 0)]
        [string[]] $Name,

        [string] $LockPath,

        [switch] $NoPathFallback,

        # Test-only escape hatch (see Test-AvmToolsLock). Hidden from help
        # and tab-completion so it does not appear in the production surface.
        [Parameter(DontShow)]
        [switch] $AllowFileUrls
    )

    Set-StrictMode -Version 3.0
    $ErrorActionPreference = 'Stop'

    $lock = if ($LockPath) {
        Read-AvmToolsLock -Path $LockPath -AllowFileUrls:$AllowFileUrls
    }
    else {
        Read-AvmToolsLock
    }
    $tools = @($lock.tools)
    $platform = Get-AvmToolPlatform
    $toolsRoot = Get-AvmFolder -Kind Tools

    if ($Name) {
        $requested = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal)
        foreach ($n in $Name) { [void]$requested.Add($n) }
        $tools = $tools | Where-Object { $requested.Contains($_.name) }
        $tools = @($tools)

        $found = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal)
        foreach ($t in $tools) { [void]$found.Add($t.name) }
        $missing = $Name | Where-Object { -not $found.Contains($_) }
        if ($missing) {
            throw [System.ArgumentException]::new(
                "Unknown tool(s) in lock: $($missing -join ', ').")
        }
    }

    foreach ($t in $tools) {
        $versionDir = Join-Path (Join-Path $toolsRoot $t.name) $t.version
        $entrypointName = if ($IsWindows) { "$($t.entrypoint).exe" } else { $t.entrypoint }
        $entrypoint = Join-Path $versionDir $entrypointName
        $verified = Join-Path $versionDir '.verified'
        $cached = (Test-Path -LiteralPath $verified) -and (Test-Path -LiteralPath $entrypoint)

        $status = 'missing'
        $path = $null
        $source = $null
        $detectedVersion = $null

        if ($cached) {
            $status = 'installed'
            $path = $entrypoint
            $source = 'cache'
        }
        elseif (-not $NoPathFallback) {
            $hit = Find-AvmToolOnPath -Entrypoint $t.entrypoint -ExpectedVersion $t.version
            if ($hit) {
                $path = $hit.Path
                $source = 'path'
                $detectedVersion = $hit.DetectedVersion
                if ($hit.Matches) {
                    $status = 'installed-on-path'
                }
                else {
                    $status = 'outdated-on-path'
                    Write-Warning ("PATH '$($t.entrypoint)' reports '{0}' but lock pins '{1}'." -f
                        ($detectedVersion ?? '<unknown>'), $t.version)
                }
            }
        }

        [pscustomobject][ordered]@{
            Name            = $t.name
            Version         = $t.version
            Platform        = $platform
            Status          = $status
            Path            = $path
            Source          = $source
            DetectedVersion = $detectedVersion
        }
    }
}