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 |