g-error-vectors.ps1

$SecretPattern = '\.env$|\.key$|\.pem$|\.pfx$|\.p12$|credentials|secrets?\.|id_rsa|id_ed25519|\.token$|\.npmrc$|\.pypirc$|\.dockercfg$|password$|apikey|oauth|jwt|\.aws[/\\]|\.kube[/\\]|\.ssh[/\\]'

$ErrorVectors = @{
    B_CLASS   = @('no feature branch', 'currently on base branch', 'on default branch')
    W_CLASS   = @('no rename script', 'currently on wip branch', 'wip branch detected')
    BEHIND    = @('behind', 'diverged', 'fetch first', 'not up to date', 'would be overwritten')
    NO_PUSH   = @('nothing to stage', 'unpushed commits', 'ahead of origin')
    NO_REMOTE = @('no such remote', 'does not appear', 'repository not found')
    AUTH      = @('authentication failed', 'permission denied', 'could not read username')
    CONFLICT  = @('conflict', 'merge conflict', 'cannot merge', 'automatic merge failed')
    NO_BRANCH = @('unknown revision', 'not a valid object', 'pathspec did not match')
    CHECKS    = @('required status checks', 'failing checks', 'check failed', 'status check', 'blocked')
    NO_PR     = @('no pull requests', 'pull request not found', 'no open pull requests')
    PROTECTED = @('protected branch', 'cannot force push', 'push declined')
}

function Resolve-OutputToVector {
    param([string]$Output)
    # ToLower once; Contains is sufficient for these terse, unambiguous error strings
    $lower = $Output.ToLower()
    foreach ($dim in $ErrorVectors.Keys) {
        foreach ($token in $ErrorVectors[$dim]) {
            if ($lower.Contains($token)) { return $dim }
        }
    }
    return $null
}

function Format-GapLabel {
    param(
        [string]$Class,
        [string]$Dim,
        [int]$Ahead  = 0,
        [int]$Behind = 0,
        [string]$Pr  = ''
    )
    $classStr = switch ($Class) { 'B' {'base'} 'W' {'wip'} 'F' {'feature'} default {$Class.ToLower()} }
    $dimStr   = switch ($Dim) {
        'B_CLASS' { "transition from $classStr to feature branch" }
        'W_CLASS' { "rename from $classStr branch to feature name" }
        'BEHIND'  { "sync $Behind commit$(if ($Behind -ne 1) {'s'}) from base" }
        'CHECKS'  { "resolve failing checks ($Pr)" }
        'NO_PUSH' { "push $Ahead unpushed commit$(if ($Ahead -ne 1) {'s'}) to origin" }
        default   { $Dim.ToLower() -replace '_',' ' }
    }
    return "GAP[$Dim]: no script to $dimStr"
}

function Resolve-MatrixAction {
    param([string]$Hash, [string]$ErrorVector = $null)
    $parts = $Hash -split '\|'
    if ($parts.Count -ne 6) { return $null }
    $class  = $parts[0]; $dirty = $parts[1]
    $ahead  = [int]($parts[2] -replace 'a', '')
    $behind = [int]($parts[3] -replace 'b', '')
    $push   = $parts[4]; $pr = $parts[5]
    $action = $null; $dim = $null
    if ($class -eq 'B') {
        $dim = 'B_CLASS'; $action = 'gitbox b "<feature-name>"'
    } elseif ($class -eq 'W') {
        $dim = 'W_CLASS'; $action = 'gitbox r "<feature-name>"'
    } elseif ($class -eq 'F') {
        if    ($dirty -like 's*')                    { $action = 'gitbox c is blocked -- remove secret-pattern files from working tree' }
        elseif ($behind -gt 0)                       { $dim = 'BEHIND'; $action = 'gitbox s' }
        elseif ($pr -eq 'PRX')                       { $dim = 'CHECKS'; $action = 'fix CI failures, then: gitbox x' }
        elseif ($pr -in @('PRO','PRA'))               { $action = if ($dirty -like 'd*') { 'gitbox land "<message>"' } else { 'gitbox ship' } }
        elseif ($pr -eq 'PRD')                        { $action = if ($dirty -like 'd*') { 'gitbox c "<message>" (draft PR open)' } else { 'mark PR ready: gh pr ready' } }
        elseif ($pr -eq 'PR-') {
            if    ($dirty -like 'd*')                { $action = 'gitbox c "<message>"' }
            elseif ($ahead -gt 0 -and $push -eq 'P') { $dim = 'NO_PR'; $action = 'gitbox o "<PR title>"' }
            elseif ($ahead -gt 0 -and $push -eq 'U') { $dim = 'NO_PUSH'; $action = 'gitbox uo "<PR title>"' }
            elseif ($ahead -eq 0 -and $dirty -eq 'c') { $action = 'nothing to do; make changes first' }
        }
    } else { $action = "unrecognised branch class '$class'" }
    # Error vector overrides for failure classes the hash cannot express after re-scan
    if ($ErrorVector) {
        switch ($ErrorVector) {
            'AUTH'      { $action = 'fix auth: gh auth login'; $dim = $null }
            'CONFLICT'  { $action = 'resolve conflict: git status -> fix files -> git add -> git rebase --continue'; $dim = $null }
            'PROTECTED' { $action = 'branch is protected -- check branch protection rules'; $dim = $null }
            'NO_REMOTE' { $action = 'no remote: git remote add origin <url>'; $dim = $null }
            'NO_BRANCH' { $action = 'branch not found -- check: git branch -a'; $dim = $null }
        }
    }
    return [pscustomobject]@{ Action = $action; Dim = $dim; Class = $class; Ahead = $ahead; Behind = $behind; Pr = $pr }
}

. (Join-Path $PSScriptRoot 'g-registry.ps1')