VirtualDeveloper.psm1

<#
.SYNOPSIS
    Core logic for the Virtual Developer Agent.
.DESCRIPTION
    Contains pure functions for filtering comments, detecting state, and building API payloads.
    This module is self-contained with all dependencies bundled in the Private folder.
 
    Copyright (c) Microsoft Corporation.
    Licensed under the MIT License.
#>


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
    )

    # Use the bundled script from the module's Private folder
    $scriptPath = Join-Path $PSScriptRoot "Private\start-virtualdeveloper-session.ps1"
    
    if (-not (Test-Path $scriptPath)) {
        throw "Could not find start-virtualdeveloper-session.ps1 in module. Module may be corrupted."
    }

    # 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