Public/Get-ProjectContext.ps1

function Get-ProjectContext {
    <#
    .SYNOPSIS
        Generate a comprehensive project summary for AI tools.
 
    .DESCRIPTION
        Creates a context summary of the current project including
        project type detection, key files, dependencies, git status,
        and available scripts/commands.
 
    .PARAMETER Path
        Path to project directory (defaults to current directory).
 
    .PARAMETER Brief
        Output a short summary only.
 
    .PARAMETER AsJson
        Output as JSON for MCP tools.
 
    .PARAMETER Copy
        Copy output to clipboard.
 
    .EXAMPLE
        Get-ProjectContext
        context -Brief
        context -Copy
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        [string]$Path = '.',

        [switch]$Brief,
        [switch]$AsJson,
        [switch]$Copy
    )

    $Path = Resolve-Path $Path -ErrorAction SilentlyContinue
    if (-not $Path) {
        Write-Host "Path not found" -ForegroundColor Red
        return
    }

    $context = [ordered]@{
        path = $Path.ToString()
        name = Split-Path $Path -Leaf
        type = $null
        framework = $null
        dependencies = @()
        scripts = @()
        git = $null
        structure = @()
        environment = [ordered]@{
            os = 'Windows'
            shell = 'PowerShell'
            wsl = (Get-Command wsl -ErrorAction SilentlyContinue) -ne $null
        }
    }

    function Get-ContextProjectType {
        param([string]$ProjectPath)

        if ((Test-Path "$ProjectPath\artisan") -and (Test-Path "$ProjectPath\composer.json")) {
            $composer = Get-Content "$ProjectPath\composer.json" -Raw | ConvertFrom-Json -ErrorAction SilentlyContinue
            if ($composer.require.'laravel/framework') {
                return @{ type = 'PHP'; framework = 'Laravel' }
            }
        }

        if (Test-Path "$ProjectPath\symfony.lock") {
            return @{ type = 'PHP'; framework = 'Symfony' }
        }

        if (Test-Path "$ProjectPath\package.json") {
            $pkg = Get-Content "$ProjectPath\package.json" -Raw | ConvertFrom-Json -ErrorAction SilentlyContinue
            if ($pkg.dependencies.react -or $pkg.devDependencies.react) {
                $fw = 'React'
                if ($pkg.dependencies.next -or $pkg.devDependencies.next) { $fw = 'Next.js' }
                return @{ type = 'JavaScript/Node'; framework = $fw }
            }
            if ($pkg.dependencies.vue -or $pkg.devDependencies.vue) {
                $fw = 'Vue'
                if ($pkg.dependencies.nuxt -or $pkg.devDependencies.nuxt) { $fw = 'Nuxt' }
                return @{ type = 'JavaScript/Node'; framework = $fw }
            }
            if ($pkg.dependencies.express) {
                return @{ type = 'JavaScript/Node'; framework = 'Express' }
            }
            return @{ type = 'JavaScript/Node'; framework = $null }
        }

        if ((Test-Path "$ProjectPath\composer.json") -or (Get-ChildItem "$ProjectPath\*.php" -ErrorAction SilentlyContinue | Select-Object -First 1)) {
            return @{ type = 'PHP'; framework = $null }
        }

        if ((Test-Path "$ProjectPath\Makefile.PL") -or (Test-Path "$ProjectPath\cpanfile") -or
            (Get-ChildItem "$ProjectPath\*.pl" -ErrorAction SilentlyContinue | Select-Object -First 1) -or
            (Get-ChildItem "$ProjectPath\*.pm" -ErrorAction SilentlyContinue | Select-Object -First 1)) {
            return @{ type = 'Perl'; framework = $null }
        }

        if ((Test-Path "$ProjectPath\requirements.txt") -or (Test-Path "$ProjectPath\pyproject.toml") -or (Test-Path "$ProjectPath\setup.py")) {
            $fw = $null
            if (Test-Path "$ProjectPath\manage.py") { $fw = 'Django' }
            if ((Test-Path "$ProjectPath\app.py") -or (Test-Path "$ProjectPath\wsgi.py")) { $fw = 'Flask' }
            return @{ type = 'Python'; framework = $fw }
        }

        return @{ type = 'Unknown'; framework = $null }
    }

    function Get-ContextDependencies {
        param([string]$ProjectPath)

        $deps = @()

        if (Test-Path "$ProjectPath\package.json") {
            $pkg = Get-Content "$ProjectPath\package.json" -Raw | ConvertFrom-Json -ErrorAction SilentlyContinue
            if ($pkg.dependencies) {
                $pkg.dependencies.PSObject.Properties | ForEach-Object {
                    $deps += @{ name = $_.Name; version = $_.Value; type = 'npm' }
                }
            }
        }

        if (Test-Path "$ProjectPath\composer.json") {
            $composer = Get-Content "$ProjectPath\composer.json" -Raw | ConvertFrom-Json -ErrorAction SilentlyContinue
            if ($composer.require) {
                $composer.require.PSObject.Properties | Where-Object { $_.Name -ne 'php' } | ForEach-Object {
                    $deps += @{ name = $_.Name; version = $_.Value; type = 'composer' }
                }
            }
        }

        if (Test-Path "$ProjectPath\cpanfile") {
            Get-Content "$ProjectPath\cpanfile" | ForEach-Object {
                if ($_ -match "requires\s+'([^']+)'") {
                    $deps += @{ name = $Matches[1]; version = '*'; type = 'cpan' }
                }
            }
        }

        if (Test-Path "$ProjectPath\requirements.txt") {
            Get-Content "$ProjectPath\requirements.txt" | ForEach-Object {
                if ($_ -match '^([a-zA-Z0-9_-]+)') {
                    $deps += @{ name = $Matches[1]; version = '*'; type = 'pip' }
                }
            }
        }

        return $deps
    }

    function Get-ContextAvailableScripts {
        param([string]$ProjectPath)

        $scripts = @()

        if (Test-Path "$ProjectPath\package.json") {
            $pkg = Get-Content "$ProjectPath\package.json" -Raw | ConvertFrom-Json -ErrorAction SilentlyContinue
            if ($pkg.scripts) {
                $pkg.scripts.PSObject.Properties | ForEach-Object {
                    $scripts += @{ name = "npm run $($_.Name)"; command = $_.Value }
                }
            }
        }

        if (Test-Path "$ProjectPath\composer.json") {
            $composer = Get-Content "$ProjectPath\composer.json" -Raw | ConvertFrom-Json -ErrorAction SilentlyContinue
            if ($composer.scripts) {
                $composer.scripts.PSObject.Properties | ForEach-Object {
                    $scripts += @{ name = "composer $($_.Name)"; command = $_.Value }
                }
            }
        }

        if (Test-Path "$ProjectPath\artisan") {
            $scripts += @{ name = 'php artisan (art)'; command = 'Laravel CLI' }
        }

        return $scripts
    }

    function Get-ContextGitInfo {
        param([string]$ProjectPath)

        if (-not (Test-Path "$ProjectPath\.git")) { return $null }

        Push-Location $ProjectPath
        try {
            $branch = git branch --show-current 2>$null
            $status = git status --porcelain 2>$null
            $remotes = git remote -v 2>$null | Select-Object -First 1
            $ahead = 0
            $behind = 0

            $tracking = git rev-parse --abbrev-ref '@{upstream}' 2>$null
            if ($tracking) {
                $counts = git rev-list --left-right --count "HEAD...$tracking" 2>$null
                if ($counts -match '(\d+)\s+(\d+)') {
                    $ahead = [int]$Matches[1]
                    $behind = [int]$Matches[2]
                }
            }

            $modified = ($status | Where-Object { $_ -match '^\s*M' }).Count
            $added = ($status | Where-Object { $_ -match '^\?\?' }).Count
            $deleted = ($status | Where-Object { $_ -match '^\s*D' }).Count

            return [ordered]@{
                branch = $branch
                remote = if ($remotes -match '\s+(\S+)\s+') { $Matches[1] } else { $null }
                ahead = $ahead
                behind = $behind
                modified = $modified
                untracked = $added
                deleted = $deleted
                clean = ($status.Count -eq 0)
            }
        } finally {
            Pop-Location
        }
    }

    function Get-ContextProjectStructure {
        param([string]$ProjectPath)

        $items = Get-ChildItem $ProjectPath -Force | Where-Object {
            $_.Name -notin @('node_modules', 'vendor', '.git', '__pycache__', '.idea', '.vscode', 'venv', '.venv')
        } | Sort-Object { -not $_.PSIsContainer }, Name | Select-Object -First 20

        $structure = @()
        foreach ($item in $items) {
            $type = if ($item.PSIsContainer) { 'dir' } else { 'file' }
            $structure += @{ name = $item.Name; type = $type }
        }

        return $structure
    }

    $projectType = Get-ContextProjectType -ProjectPath $Path
    $context.type = $projectType.type
    $context.framework = $projectType.framework
    $context.dependencies = Get-ContextDependencies -ProjectPath $Path
    $context.scripts = Get-ContextAvailableScripts -ProjectPath $Path
    $context.git = Get-ContextGitInfo -ProjectPath $Path
    $context.structure = Get-ContextProjectStructure -ProjectPath $Path

    if ($AsJson) {
        $output = $context | ConvertTo-Json -Depth 10
        if ($Copy) {
            $output | Set-Clipboard
            Write-Host "JSON context copied to clipboard" -ForegroundColor Green
        }
        $output
        return
    }

    $output = ""

    if ($Brief) {
        $output = @"
Project: $($context.name)
Type: $($context.type)$(if ($context.framework) { " ($($context.framework))" })
Path: $($context.path)
$(if ($context.git) { "Branch: $($context.git.branch)$(if (-not $context.git.clean) { ' (modified)' })" })
Dependencies: $($context.dependencies.Count)
"@

    } else {
        $output = @"
================================================================================
PROJECT CONTEXT: $($context.name)
================================================================================
 
TYPE: $($context.type)$(if ($context.framework) { " / $($context.framework)" })
PATH: $($context.path)
 
"@


        if ($context.git) {
            $gitStatus = if ($context.git.clean) { "clean" } else { "$($context.git.modified)M $($context.git.untracked)? $($context.git.deleted)D" }
            $output += @"
GIT:
  Branch: $($context.git.branch)
  Remote: $($context.git.remote)
  Status: $gitStatus
  $(if ($context.git.ahead -gt 0) { "Ahead: $($context.git.ahead) commits" })
  $(if ($context.git.behind -gt 0) { "Behind: $($context.git.behind) commits" })
 
"@

        }

        $output += @"
STRUCTURE:
"@

        foreach ($item in $context.structure) {
            $icon = if ($item.type -eq 'dir') { '[D]' } else { '[F]' }
            $output += "`n $icon $($item.name)"
        }

        if ($context.dependencies.Count -gt 0) {
            $output += "`n`nDEPENDENCIES ($($context.dependencies.Count)):"
            $grouped = $context.dependencies | Group-Object { $_.type }
            foreach ($group in $grouped) {
                $output += "`n [$($group.Name)]"
                $group.Group | Select-Object -First 10 | ForEach-Object {
                    $output += "`n - $($_.name)"
                }
                if ($group.Group.Count -gt 10) {
                    $output += "`n ... and $($group.Group.Count - 10) more"
                }
            }
        }

        if ($context.scripts.Count -gt 0) {
            $output += "`n`nAVAILABLE SCRIPTS:"
            foreach ($s in $context.scripts | Select-Object -First 10) {
                $output += "`n - $($s.name)"
            }
        }

        $output += @"
 
================================================================================
ENVIRONMENT: Windows / PowerShell$(if ($context.environment.wsl) { ' / WSL available' })
================================================================================
"@

    }

    if ($Copy) {
        $output | Set-Clipboard
        Write-Host "Context copied to clipboard" -ForegroundColor Green
        Write-Host ""
    }

    Write-Output $output
}