Private/Helpers.ps1

# Private helper functions for CodeCompass.Repo

#region Standards

$script:Standards = @{
    core    = @{
        files_required         = @('README.md', '.gitignore', 'LICENSE', 'CHANGELOG.md', '.editorconfig', 'CODEOWNERS')
        files_recommended      = @('CONTRIBUTING.md', '.github/PULL_REQUEST_TEMPLATE.md', '.github/ISSUE_TEMPLATE/')
        structure_source       = 'src'
        structure_tests        = 'tests'
        structure_docs         = 'docs'
        require_ci             = $true
        require_test_step      = $true
        require_lint_step      = $true
        readme_sections        = @('Overview', 'Installation', 'Usage')
        changelog_format       = 'keep-a-changelog'
        require_lockfile       = $true
        require_dep_automation = $true
        security_forbidden     = @('*.pem', '*.key', '*.pfx', '*secret*', '*credentials*')
    }
    active  = @{
        files_required         = @('README.md', '.gitignore', 'LICENSE')
        files_recommended      = @()
        structure_source       = 'src'
        structure_tests        = 'tests'
        structure_docs         = $null
        require_ci             = $true
        require_test_step      = $false
        require_lint_step      = $false
        readme_sections        = @('Overview', 'Installation', 'Usage')
        changelog_format       = $null
        require_lockfile       = $true
        require_dep_automation = $false
        security_forbidden     = @('*.pem', '*.key', '*.pfx', '*secret*', '*credentials*')
    }
    minimal = @{
        files_required         = @('README.md', '.gitignore')
        files_recommended      = @()
        structure_source       = $null
        structure_tests        = $null
        structure_docs         = $null
        require_ci             = $false
        require_test_step      = $false
        require_lint_step      = $false
        readme_sections        = @()
        changelog_format       = $null
        require_lockfile       = $false
        require_dep_automation = $false
        security_forbidden     = @('*.pem', '*.key', '*.pfx')
    }
}

#endregion

#region Check Result Factory

function New-CCRepoCheckResult {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$CheckId,
        [Parameter(Mandatory)][string]$Category,
        [Parameter(Mandatory)][string]$Item,
        [Parameter(Mandatory)][ValidateSet('Pass', 'Fail', 'Warning', 'Skipped')][string]$Status,
        [Parameter(Mandatory)][ValidateSet('Error', 'Warning', 'Info')][string]$Severity,
        [Parameter(Mandatory)][string]$Message,
        [string]$Standard = 'core',
        [bool]$FixAvailable = $false,
        [string]$FixAction = ''
    )

    [PSCustomObject]@{
        CheckId      = $CheckId
        Category     = $Category
        Item         = $Item
        Status       = $Status
        Severity     = $Severity
        Message      = $Message
        FixAvailable = $FixAvailable
        Standard     = $Standard
        FixAction    = $FixAction
    }
}

#endregion

#region Config Loading

function ConvertFrom-CCRepoYaml {
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Content)

    $result = @{}
    $currentSection = $null

    foreach ($line in $Content -split "`n") {
        $line = $line.TrimEnd()
        if ($line -match '^\s*#' -or $line -match '^\s*$') { continue }

        if ($line -match '^(\w[\w_]*):\s*$') {
            $currentSection = $Matches[1]
            $result[$currentSection] = @{}
        }
        elseif ($line -match '^(\w[\w_]*):\s+(.+)$') {
            $key = $Matches[1]
            $value = $Matches[2].Trim()
            if ($value -eq 'true') { $value = $true }
            elseif ($value -eq 'false') { $value = $false }
            elseif ($value -match '^\d+$') { $value = [int]$value }
            $result[$key] = $value
        }
        elseif ($line -match '^\s+(\w[\w_]*):\s+(.+)$' -and $currentSection) {
            $key = $Matches[1]
            $value = $Matches[2].Trim()
            if ($value -eq 'true') { $value = $true }
            elseif ($value -eq 'false') { $value = $false }
            elseif ($value -match '^\d+$') { $value = [int]$value }
            $result[$currentSection][$key] = $value
        }
        elseif ($line -match '^\s+-\s+(.+)$') {
            $value = $Matches[1].Trim()
            if ($currentSection -and $result[$currentSection] -is [hashtable]) {
                # Find last key in current section that should be an array
                $lastKey = $result[$currentSection].Keys | Select-Object -Last 1
                if ($lastKey -and $result[$currentSection][$lastKey] -isnot [array]) {
                    $result[$currentSection][$lastKey] = @($result[$currentSection][$lastKey])
                }
                if ($lastKey) {
                    $result[$currentSection][$lastKey] += $value
                }
            }
            elseif (-not $currentSection) {
                $lastKey = $result.Keys | Where-Object { $result[$_] -is [array] -or $result[$_] -is [string] } | Select-Object -Last 1
                if ($lastKey) {
                    if ($result[$lastKey] -isnot [array]) { $result[$lastKey] = @() }
                    $result[$lastKey] += $value
                }
            }
        }
    }
    $result
}

function Get-CCRepoConfig {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$Path,
        [string]$Standard = 'core',
        [string]$ConfigFile
    )

    if (-not $script:Standards.ContainsKey($Standard)) {
        throw "Unknown standard: '$Standard'. Valid values: $($script:Standards.Keys -join ', ')"
    }

    $config = $script:Standards[$Standard].Clone()

    # Load per-repo config if it exists
    $repoConfig = $null
    if ($ConfigFile -and (Test-Path $ConfigFile)) {
        $content = Get-Content $ConfigFile -Raw
        $repoConfig = ConvertFrom-CCRepoYaml -Content $content
    }
    elseif (Test-Path (Join-Path $Path '.codecompass/config.yml')) {
        $content = Get-Content (Join-Path $Path '.codecompass/config.yml') -Raw
        $repoConfig = ConvertFrom-CCRepoYaml -Content $content
    }

    if ($repoConfig) {
        # Apply structure overrides
        if ($repoConfig.ContainsKey('structure')) {
            if ($repoConfig['structure'].ContainsKey('source')) {
                $config['structure_source'] = $repoConfig['structure']['source']
            }
            if ($repoConfig['structure'].ContainsKey('tests')) {
                $config['structure_tests'] = $repoConfig['structure']['tests']
            }
            if ($repoConfig['structure'].ContainsKey('docs')) {
                $config['structure_docs'] = $repoConfig['structure']['docs']
            }
        }

        # Apply disabled checks
        if ($repoConfig.ContainsKey('checks') -and $repoConfig['checks'].ContainsKey('disabled')) {
            $config['disabled_checks'] = $repoConfig['checks']['disabled']
        }

        # Apply additional required files
        if ($repoConfig.ContainsKey('files') -and $repoConfig['files'].ContainsKey('required')) {
            $additional = $repoConfig['files']['required']
            if ($additional -is [array]) {
                $config['files_required'] = $config['files_required'] + $additional
            }
        }
    }

    $config
}

#endregion

#region Technology Detection

function Get-CCTechStack {
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Path)

    $detected = @()

    if ((Get-ChildItem $Path -Filter '*.sln' -ErrorAction SilentlyContinue) -or
        (Get-ChildItem $Path -Filter '*.csproj' -Recurse -Depth 2 -ErrorAction SilentlyContinue)) {
        $detected += 'dotnet'
    }
    if ((Get-ChildItem $Path -Filter '*.psd1' -ErrorAction SilentlyContinue) -or
        (Get-ChildItem $Path -Filter '*.psm1' -ErrorAction SilentlyContinue)) {
        $detected += 'powershell'
    }
    if (Test-Path (Join-Path $Path 'package.json')) {
        $detected += 'javascript'
    }
    if ((Test-Path (Join-Path $Path 'pyproject.toml')) -or
        (Test-Path (Join-Path $Path 'requirements.txt'))) {
        $detected += 'python'
    }
    if ((Get-ChildItem $Path -Filter '*.xcodeproj' -ErrorAction SilentlyContinue) -or
        (Test-Path (Join-Path $Path 'Podfile'))) {
        $detected += 'ios'
    }
    if (Test-Path (Join-Path $Path 'build.gradle')) {
        $detected += 'android'
    }

    $detected
}

#endregion