workflows/default/systems/runtime/modules/prompt-builder.ps1

<#
.SYNOPSIS
Prompt building utilities for task execution

.DESCRIPTION
Provides functions for building prompts from templates with variable substitution
#>


function Build-TaskPrompt {
    <#
    .SYNOPSIS
    Build a complete task prompt from template and task data

    .PARAMETER PromptTemplate
    The template string containing {{VARIABLE}} placeholders

    .PARAMETER Task
    Task object containing task properties

    .PARAMETER SessionId
    Current session ID

    .PARAMETER ProductMission
    Product mission description or file reference

    .PARAMETER EntityModel
    Entity model description or file reference

    .PARAMETER StandardsList
    Formatted list of applicable standards

    .OUTPUTS
    String containing the completed prompt
    #>

    param(
        [Parameter(Mandatory = $true)]
        [string]$PromptTemplate,

        [Parameter(Mandatory = $true)]
        [object]$Task,

        [Parameter(Mandatory = $true)]
        [string]$SessionId,

        [Parameter(Mandatory = $false)]
        [string]$ProductMission = "No product mission file found.",

        [Parameter(Mandatory = $false)]
        [string]$EntityModel = "No entity model file found.",

        [Parameter(Mandatory = $false)]
        [string]$StandardsList = "No standards files found.",

        [Parameter(Mandatory = $false)]
        [string]$InstanceId = ""
    )

    # Start with template
    $prompt = $PromptTemplate

    # Replace basic task info
    $taskId = if ($Task.id) { "$($Task.id)" } else { "" }
    $taskIdShort = if ($taskId.Length -gt 8) { $taskId.Substring(0, 8) } else { $taskId }

    $instanceIdShort = ""
    if ($InstanceId) {
        $guidMatch = [regex]::Match($InstanceId, '^[0-9a-fA-F]{8}')
        if ($guidMatch.Success) {
            $instanceIdShort = $guidMatch.Value.ToLowerInvariant()
        }
    }

    $prompt = $prompt -replace '\{\{SESSION_ID\}\}', $SessionId
    $prompt = $prompt -replace '\{\{TASK_ID\}\}', $taskId
    $prompt = $prompt -replace '\{\{TASK_ID_SHORT\}\}', $taskIdShort
    $prompt = $prompt -replace '\{\{TASK_NAME\}\}', $Task.name
    $prompt = $prompt -replace '\{\{TASK_CATEGORY\}\}', $Task.category
    $prompt = $prompt -replace '\{\{TASK_PRIORITY\}\}', $Task.priority
    $prompt = $prompt -replace '\{\{TASK_DESCRIPTION\}\}', $Task.description
    $prompt = $prompt -replace '\{\{PRODUCT_MISSION\}\}', $ProductMission
    $prompt = $prompt -replace '\{\{ENTITY_MODEL\}\}', $EntityModel
    $prompt = $prompt -replace '\{\{INSTANCE_ID\}\}', $InstanceId
    $prompt = $prompt -replace '\{\{INSTANCE_ID_SHORT\}\}', $instanceIdShort
    # Format and replace applicable standards
    $applicableStandards = ""
    if ($Task.applicable_standards -and $Task.applicable_standards.Count -gt 0) {
        $applicableStandards = ($Task.applicable_standards | ForEach-Object { "- $_" }) -join "`n"
    } else {
        $applicableStandards = "No specific standards listed for this task - use global standards from .bot/recipes/standards/global/"
    }
    $prompt = $prompt -replace '\{\{APPLICABLE_STANDARDS\}\}', $applicableStandards

    # Format and replace applicable agents
    $applicableAgents = ""
    if ($Task.applicable_agents -and $Task.applicable_agents.Count -gt 0) {
        $applicableAgents = ($Task.applicable_agents | ForEach-Object { "- $_" }) -join "`n"
    } else {
        $applicableAgents = "Use .bot/recipes/agents/implementer/AGENT.md as your default persona"
    }
    $prompt = $prompt -replace '\{\{APPLICABLE_AGENTS\}\}', $applicableAgents

    # Format and replace applicable skills
    $applicableSkills = ""
    if ($Task.applicable_skills -and $Task.applicable_skills.Count -gt 0) {
        $applicableSkills = ($Task.applicable_skills | ForEach-Object { "- $_" }) -join "`n"
    } else {
        $applicableSkills = "No specific skills listed — use judgement based on task category"
    }
    $prompt = $prompt -replace '\{\{APPLICABLE_SKILLS\}\}', $applicableSkills

    # Format and replace acceptance criteria
    $acceptanceCriteria = if ($Task.acceptance_criteria) {
        ($Task.acceptance_criteria | ForEach-Object { "- $_" }) -join "`n"
    } else {
        "No specific acceptance criteria defined."
    }
    $prompt = $prompt -replace '\{\{ACCEPTANCE_CRITERIA\}\}', $acceptanceCriteria

    # Format and replace steps
    $steps = if ($Task.steps) {
        ($Task.steps | ForEach-Object { "- $_" }) -join "`n"
    } else {
        "No specific steps defined."
    }
    $prompt = $prompt -replace '\{\{TASK_STEPS\}\}', $steps

    # Replace standards list
    $prompt = $prompt -replace '\{\{STANDARDS_LIST\}\}', $StandardsList

    # Format and replace questions resolved (user decisions from analysis Q&A)
    $questionsResolved = ""
    if ($Task.questions_resolved -and $Task.questions_resolved.Count -gt 0) {
        $questionsResolved = "The following decisions were made by the user during analysis. You **MUST** honour them — do not contradict or override these answers.`n`n"
        foreach ($qa in $Task.questions_resolved) {
            $questionsResolved += "**Q:** $($qa.question)`n"
            $questionsResolved += "**A:** $($qa.answer)`n`n"
        }
    }
    $prompt = $prompt -replace '\{\{QUESTIONS_RESOLVED\}\}', $questionsResolved

    # Add steering protocol include
    $steeringProtocolPath = Join-Path $PSScriptRoot "..\..\prompts\workflows\92-steering-protocol.include.md"
    $steeringProtocol = ""
    if (Test-Path $steeringProtocolPath) {
        $steeringProtocol = Get-Content $steeringProtocolPath -Raw -ErrorAction SilentlyContinue
    }
    $prompt = $prompt -replace '\{\{STEERING_PROTOCOL\}\}', $steeringProtocol

    return $prompt
}