VirtualDeveloper.psm1

<#
.SYNOPSIS
    Core logic for the Virtual Developer Agent.
.DESCRIPTION
    Contains pure functions for filtering comments, detecting state, and building API payloads.
 
    Copyright (c) Microsoft Corporation.
    Licensed under the MIT License.
#>


# Get the module's installation directory
$script:ModuleRoot = $PSScriptRoot

# Try to find the scripts directory (either in repo or installed location)
$script:ScriptsRoot = $null
$possiblePaths = @(
    (Join-Path $PSScriptRoot "..\..\scripts"),           # When in repo: modules/VirtualDeveloper -> scripts
    (Join-Path $PSScriptRoot "..\scripts"),              # Alternative layout
    (Join-Path $env:USERPROFILE "VirtualDeveloper\scripts")  # User install location
)
foreach ($p in $possiblePaths) {
    if (Test-Path $p) {
        $script:ScriptsRoot = (Resolve-Path $p).Path
        break
    }
}

function Select-UserComment {
    <#
    .SYNOPSIS
        Filters out comments made by the agent itself.
    #>

    param(
        [Parameter(Mandatory=$false)] [array] $Comments,
        [Parameter(Mandatory=$true)] [string] $AgentIdentity
    )
    if (-not $Comments) { return @() }
    return $Comments | Where-Object { $_.CreatedBy.UniqueName -ne $AgentIdentity }
}

function Test-IsFrozen {
    <#
    .SYNOPSIS
        Detects if the conversation is frozen/ready for breakdown.
    #>

    param(
        [Parameter(Mandatory=$false)] [array] $Comments
    )
    if (-not $Comments) { return $false }
    foreach ($c in $Comments) {
        if ($c.Text -match '#frozen') {
            return $true
        }
    }
    return $false
}

function New-WorkItemPatchParam {
    <#
    .SYNOPSIS
        Constructs the JSON Patch payload for ADO updates.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Pure function creating an object')]
    param(
        [Parameter(Mandatory)] [int] $Id,
        [Parameter(Mandatory)] [hashtable] $Updates,
        [Parameter(Mandatory)] [string] $ETag
    )
    
    $patchOps = @()
    foreach ($key in $Updates.Keys) {
        $patchOps += @{
            op = "add"
            path = "/fields/$key"
            value = $Updates[$key]
        }
    }

    return @{
        Uri = "https://dev.azure.com/{org}/{project}/_apis/wit/workitems/$Id`?api-version=7.0"
        Method = "Patch"
        Headers = @{
            "If-Match" = $ETag
            "Content-Type" = "application/json-patch+json"
        }
        Body = ($patchOps | ConvertTo-Json -Depth 10)
    }
}

function Invoke-VirtualDeveloper {
    <#
    .SYNOPSIS
        Runs the Virtual Developer Agent against an Azure DevOps Work Item.
    .DESCRIPTION
        Analyzes the work item, invokes AI reasoning, and updates the work item
        with comments or description expansions.
    .PARAMETER WorkItemId
        The Azure DevOps Work Item ID to process.
    .PARAMETER AgentIdentity
        The display name of the agent (used to filter out its own comments).
    .PARAMETER MockAgent
        If specified, uses canned responses instead of calling Copilot.
    .EXAMPLE
        Invoke-VirtualDeveloper -WorkItemId 12345
    .EXAMPLE
        Invoke-VirtualDeveloper -WorkItemId 12345 -MockAgent
    #>

    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
        [Parameter(Mandatory=$true)]
        [int]$WorkItemId,
        
        [Parameter(Mandatory=$false)]
        [string]$AgentIdentity = "Virtual Developer",
        
        [Parameter(Mandatory=$false)]
        [switch]$MockAgent
    )

    # Find the main script
    $scriptPath = $null
    $searchPaths = @(
        (Join-Path $script:ScriptsRoot "start-virtualdeveloper-session.ps1"),
        (Join-Path $PSScriptRoot "..\..\scripts\start-virtualdeveloper-session.ps1"),
        "C:\work\ai_agent\scripts\start-virtualdeveloper-session.ps1"
    )
    
    foreach ($p in $searchPaths) {
        if ($p -and (Test-Path $p)) {
            $scriptPath = (Resolve-Path $p).Path
            break
        }
    }

    if (-not $scriptPath) {
        throw "Could not find start-virtualdeveloper-session.ps1. Please ensure the VirtualDeveloper repository is available or set the script location."
    }

    # Execute the script in a fresh PowerShell process to avoid scope conflicts
    $argList = @("-File", $scriptPath, "-WorkItemId", $WorkItemId, "-AgentIdentity", $AgentIdentity)
    if ($MockAgent) {
        $argList += "-MockAgent"
    }
    
    & pwsh @argList
}

Export-ModuleMember -Function Select-UserComment, Test-IsFrozen, New-WorkItemPatchParam, Invoke-VirtualDeveloper