tools/Test-GitEasyProjectState.ps1

# =============================================================================
# Script : Test-GitEasyProjectState.ps1
# Author : Keith Ramsey
# Created : 2026-05-27
# =============================================================================
# Change Log
# -----------------------------------------------------------------------------
# 2026-05-27 cross-suite meta Claude Initial: project-state checker per the
# suite convention (Tools/Test-*ProjectState.ps1).
# Lean shape — required folders, manifest sanity,
# manifest-export <-> Public/ parity. Pairs with
# Run-GitEasyPester.ps1 (existing) and the existing
# audit tools in Tools/ (Audit-PublicJargon,
# Audit-GEUsage), which cover deeper concerns.
# =============================================================================

[CmdletBinding()]
param(
    [string] $ProjectRoot,
    [switch] $CiMode
)

if (-not $ProjectRoot) {
    $ProjectRoot = Split-Path -Parent (Split-Path -Parent $PSCommandPath)
}

$results = New-Object System.Collections.Generic.List[object]

function Add-Result {
    <#
    .DESCRIPTION
    Internal. Appends a check result record to the script-scoped results list.
    Steps:
    1. Build a result object with check name, status, outcome, error category, and path.
    2. Add the object to the results list.
    #>

    param(
        [string] $Check,
        [string] $Status,
        [string] $Outcome,
        [string] $ErrorCategory = 'None',
        [string] $Path = ''
    )
    $results.Add([pscustomobject]@{
        Check         = $Check
        Status        = $Status
        Outcome       = $Outcome
        ErrorCategory = $ErrorCategory
        Path          = $Path
    })
}

# --- Required folders --------------------------------------------------------
$required = 'Public', 'Private', 'Tests', 'Tests\Unit', 'docs', 'Tools', 'Wiki'
foreach ($r in $required) {
    $p = Join-Path $ProjectRoot $r
    if (Test-Path -LiteralPath $p -PathType Container) {
        Add-Result -Check 'RequiredFolder' -Status 'Succeeded' -Outcome "Folder present: $r" -Path $p
    } else {
        Add-Result -Check 'RequiredFolder' -Status 'HandledError' -Outcome "Missing folder: $r" -ErrorCategory 'MissingFolder' -Path $p
    }
}

# --- Manifest + loader exist -------------------------------------------------
$psd1 = Join-Path $ProjectRoot 'GitEasy.psd1'
$psm1 = Join-Path $ProjectRoot 'GitEasy.psm1'
foreach ($f in @($psd1, $psm1)) {
    if (Test-Path -LiteralPath $f) {
        Add-Result -Check 'ManifestPresent' -Status 'Succeeded' -Outcome "Present: $(Split-Path $f -Leaf)" -Path $f
    } else {
        Add-Result -Check 'ManifestPresent' -Status 'HandledError' -Outcome "Missing: $(Split-Path $f -Leaf)" -ErrorCategory 'MissingFile' -Path $f
    }
}

# --- FunctionsToExport matches Public/ ---------------------------------------
if (Test-Path -LiteralPath $psd1) {
    try {
        $manifest = Import-PowerShellDataFile -Path $psd1
        $declared = @($manifest.FunctionsToExport)
        $publicDir = Join-Path $ProjectRoot 'Public'
        $publicFiles = if (Test-Path -LiteralPath $publicDir) {
            @(Get-ChildItem -LiteralPath $publicDir -Filter *.ps1 -File | ForEach-Object { [System.IO.Path]::GetFileNameWithoutExtension($_.Name) })
        } else { @() }

        $missing = $publicFiles | Where-Object { $declared -notcontains $_ }
        $extra   = $declared    | Where-Object { $publicFiles -notcontains $_ }

        if ($missing.Count -eq 0 -and $extra.Count -eq 0) {
            Add-Result -Check 'ManifestExports' -Status 'Succeeded' -Outcome "Manifest exports match $($publicFiles.Count) Public/ files."
        } else {
            $msg = ''
            if ($missing.Count) { $msg += "Missing from manifest: $($missing -join ',') " }
            if ($extra.Count)   { $msg += "Declared but no file: $($extra -join ',')" }
            Add-Result -Check 'ManifestExports' -Status 'HandledError' -Outcome $msg -ErrorCategory 'ManifestMismatch' -Path $psd1
        }
    } catch {
        Add-Result -Check 'ManifestExports' -Status 'HandledError' -Outcome "Manifest load failed: $($_.Exception.Message)" -ErrorCategory 'ManifestLoadFailed' -Path $psd1
    }
}

# --- Suite-convention docs (these are the audit gaps still open) -------------
$conventionDocs = @{
    'docs\PHASE.txt'             = 'PhaseDocs'
    'docs\DECISION_REGISTER.md'  = 'PhaseDocs'
    'PSScriptAnalyzerSettings.psd1' = 'PSSAConfig'
}
foreach ($k in $conventionDocs.Keys) {
    $f = Join-Path $ProjectRoot $k
    if (Test-Path -LiteralPath $f) {
        Add-Result -Check $conventionDocs[$k] -Status 'Succeeded' -Outcome "Present: $k" -Path $f
    } else {
        Add-Result -Check $conventionDocs[$k] -Status 'HandledError' -Outcome "Missing: $k" -ErrorCategory 'MissingFile' -Path $f
    }
}

# --- Summary -----------------------------------------------------------------
$failed = @($results | Where-Object Status -eq 'HandledError')
$summary = [pscustomobject]@{
    PSTypeName    = 'GitEasy.ProjectState'
    Status        = if ($failed.Count -eq 0) { 'Succeeded' } else { 'HandledError' }
    Outcome       = "$($results.Count) checks; $($failed.Count) failures."
    ErrorCategory = if ($failed.Count -eq 0) { 'None' } else { 'StateCheckFailed' }
    Results       = $results.ToArray()
    FailureCount  = $failed.Count
    TotalCount    = $results.Count
}

if ($CiMode) {
    if ($failed.Count -eq 0) { exit 0 } else { exit 1 }
}

$summary