Sync: Complete project state with all MEGA SPRINT V1-V3 features and Codex stubs
This commit is contained in:
664
ralph/scripts/Common.ps1
Normal file
664
ralph/scripts/Common.ps1
Normal file
@@ -0,0 +1,664 @@
|
||||
Set-StrictMode -Version 3.0
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-RalphRoot {
|
||||
return (Split-Path -Parent $PSScriptRoot)
|
||||
}
|
||||
|
||||
function Get-RepoRoot {
|
||||
return (Split-Path -Parent (Get-RalphRoot))
|
||||
}
|
||||
|
||||
function Ensure-Directory {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
if (-not (Test-Path $Path)) {
|
||||
New-Item -ItemType Directory -Force -Path $Path | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Read-JsonFile {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
if (-not (Test-Path $Path)) {
|
||||
throw "JSON file not found: $Path"
|
||||
}
|
||||
|
||||
return Get-Content -Raw -Path $Path | ConvertFrom-Json
|
||||
}
|
||||
|
||||
function Get-ProvidersConfig {
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$localPath = Join-Path $ralphRoot "config\providers.local.json"
|
||||
$examplePath = Join-Path $ralphRoot "config\providers.example.json"
|
||||
|
||||
if (Test-Path $localPath) {
|
||||
return Read-JsonFile -Path $localPath
|
||||
}
|
||||
|
||||
return Read-JsonFile -Path $examplePath
|
||||
}
|
||||
|
||||
function Get-ProviderConfig {
|
||||
param([Parameter(Mandatory = $true)][string]$Name)
|
||||
|
||||
$config = Get-ProvidersConfig
|
||||
if (-not $config.providers.PSObject.Properties.Name.Contains($Name)) {
|
||||
throw "Provider '$Name' not found in providers config."
|
||||
}
|
||||
|
||||
return $config.providers.$Name
|
||||
}
|
||||
|
||||
function Get-DefaultImplementer {
|
||||
$config = Get-ProvidersConfig
|
||||
return [string]$config.default_implementer
|
||||
}
|
||||
|
||||
function Get-DefaultReviewers {
|
||||
$config = Get-ProvidersConfig
|
||||
return @($config.default_reviewers)
|
||||
}
|
||||
|
||||
function Get-CodexConfig {
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$localPath = Join-Path $ralphRoot "config\codex.local.json"
|
||||
$examplePath = Join-Path $ralphRoot "config\codex.example.json"
|
||||
|
||||
if (Test-Path $localPath) {
|
||||
return Read-JsonFile -Path $localPath
|
||||
}
|
||||
|
||||
return Read-JsonFile -Path $examplePath
|
||||
}
|
||||
|
||||
function Get-RalphAutomationConfig {
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$localPath = Join-Path $ralphRoot "config\automation.local.json"
|
||||
$examplePath = Join-Path $ralphRoot "config\automation.example.json"
|
||||
|
||||
if (Test-Path $localPath) {
|
||||
return Read-JsonFile -Path $localPath
|
||||
}
|
||||
|
||||
if (Test-Path $examplePath) {
|
||||
return Read-JsonFile -Path $examplePath
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-TelegramConfig {
|
||||
$ralphRoot = Get-RalphRoot
|
||||
$localPath = Join-Path $ralphRoot "config\telegram.local.json"
|
||||
$examplePath = Join-Path $ralphRoot "config\telegram.example.json"
|
||||
|
||||
if (Test-Path $localPath) {
|
||||
return Read-JsonFile -Path $localPath
|
||||
}
|
||||
|
||||
if (Test-Path $examplePath) {
|
||||
return Read-JsonFile -Path $examplePath
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-TelegramChatIds {
|
||||
param($Config)
|
||||
|
||||
if ($null -eq $Config) {
|
||||
return @()
|
||||
}
|
||||
|
||||
if ($Config.PSObject.Properties.Name -contains "chat_ids") {
|
||||
return @($Config.chat_ids | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } | ForEach-Object { [string]$_ })
|
||||
}
|
||||
|
||||
if ($Config.PSObject.Properties.Name -contains "chat_id" -and -not [string]::IsNullOrWhiteSpace([string]$Config.chat_id)) {
|
||||
return @([string]$Config.chat_id)
|
||||
}
|
||||
|
||||
return @()
|
||||
}
|
||||
|
||||
function Test-TelegramEventEnabled {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]$Config,
|
||||
[Parameter(Mandatory = $true)][string]$EventName
|
||||
)
|
||||
|
||||
$enabled = $false
|
||||
try { $enabled = [bool]$Config.enabled } catch { $enabled = $false }
|
||||
if (-not $enabled) {
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($Config.PSObject.Properties.Name -contains "events" -and $null -ne $Config.events) {
|
||||
$eventProperty = $Config.events.PSObject.Properties[$EventName]
|
||||
if ($null -ne $eventProperty) {
|
||||
try { return [bool]$eventProperty.Value } catch { return $false }
|
||||
}
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
function Send-TelegramNotification {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$EventName,
|
||||
[Parameter(Mandatory = $true)][string]$Title,
|
||||
[Parameter(Mandatory = $true)][string]$Message,
|
||||
[string]$RunId = "",
|
||||
[string]$Stage = "",
|
||||
[string]$Status = ""
|
||||
)
|
||||
|
||||
try {
|
||||
$config = Get-TelegramConfig
|
||||
}
|
||||
catch {
|
||||
return [ordered]@{
|
||||
sent = $false
|
||||
reason = $_.Exception.Message
|
||||
delivered = 0
|
||||
errors = @()
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -eq $config) {
|
||||
return [ordered]@{
|
||||
sent = $false
|
||||
reason = "telegram config missing"
|
||||
delivered = 0
|
||||
errors = @()
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-TelegramEventEnabled -Config $config -EventName $EventName)) {
|
||||
return [ordered]@{
|
||||
sent = $false
|
||||
reason = "event disabled"
|
||||
delivered = 0
|
||||
errors = @()
|
||||
}
|
||||
}
|
||||
|
||||
$botToken = ""
|
||||
if ($config.PSObject.Properties.Name -contains "bot_token") {
|
||||
$botToken = [string]$config.bot_token
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($botToken)) {
|
||||
return [ordered]@{
|
||||
sent = $false
|
||||
reason = "bot token missing"
|
||||
delivered = 0
|
||||
errors = @()
|
||||
}
|
||||
}
|
||||
|
||||
$chatIds = @(Get-TelegramChatIds -Config $config)
|
||||
if ($chatIds.Count -eq 0) {
|
||||
return [ordered]@{
|
||||
sent = $false
|
||||
reason = "chat ids missing"
|
||||
delivered = 0
|
||||
errors = @()
|
||||
}
|
||||
}
|
||||
|
||||
$timeoutSeconds = 8
|
||||
if ($config.PSObject.Properties.Name -contains "timeout_seconds") {
|
||||
try {
|
||||
$timeoutSeconds = [int]$config.timeout_seconds
|
||||
}
|
||||
catch {
|
||||
$timeoutSeconds = 8
|
||||
}
|
||||
}
|
||||
|
||||
$prefix = "Ralph"
|
||||
if ($config.PSObject.Properties.Name -contains "prefix" -and -not [string]::IsNullOrWhiteSpace([string]$config.prefix)) {
|
||||
$prefix = [string]$config.prefix
|
||||
}
|
||||
|
||||
$lines = @(
|
||||
("{0} | {1}" -f $prefix, $Title.Trim())
|
||||
$Message.Trim()
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($RunId)) {
|
||||
$lines += ("run: " + $RunId)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($Stage) -or -not [string]::IsNullOrWhiteSpace($Status)) {
|
||||
$statusLine = @()
|
||||
if (-not [string]::IsNullOrWhiteSpace($Stage)) {
|
||||
$statusLine += ("stage=" + $Stage)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($Status)) {
|
||||
$statusLine += ("status=" + $Status)
|
||||
}
|
||||
if ($statusLine.Count -gt 0) {
|
||||
$lines += ($statusLine -join " ")
|
||||
}
|
||||
}
|
||||
$text = ($lines -join "`n").Trim()
|
||||
|
||||
$uri = "https://api.telegram.org/bot{0}/sendMessage" -f $botToken
|
||||
$delivered = 0
|
||||
$errors = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
foreach ($chatId in $chatIds) {
|
||||
try {
|
||||
$body = @{
|
||||
chat_id = $chatId
|
||||
text = $text
|
||||
disable_web_page_preview = $true
|
||||
}
|
||||
Invoke-RestMethod -Method Post -Uri $uri -Body $body -ContentType "application/x-www-form-urlencoded" -TimeoutSec $timeoutSeconds | Out-Null
|
||||
$delivered += 1
|
||||
}
|
||||
catch {
|
||||
$errors.Add(("{0}: {1}" -f $chatId, $_.Exception.Message))
|
||||
}
|
||||
}
|
||||
|
||||
return [ordered]@{
|
||||
sent = ($delivered -gt 0)
|
||||
reason = $(if ($delivered -gt 0) { "ok" } else { "delivery failed" })
|
||||
delivered = $delivered
|
||||
errors = @($errors)
|
||||
}
|
||||
}
|
||||
|
||||
function New-RunId {
|
||||
param([string]$Label = "autopilot")
|
||||
return "{0}-{1}" -f (Get-Date -Format "yyyyMMdd-HHmmss"), $Label
|
||||
}
|
||||
|
||||
function Get-TaskFiles {
|
||||
param([string]$TaskDirectory = $(Join-Path (Get-RalphRoot) "tasks\current"))
|
||||
|
||||
return @{
|
||||
Task = Join-Path $TaskDirectory "TASK.md"
|
||||
Acceptance = Join-Path $TaskDirectory "ACCEPTANCE.md"
|
||||
Context = Join-Path $TaskDirectory "CONTEXT.md"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-RalphInboxDirectory {
|
||||
return (Join-Path (Get-RalphRoot) "tasks\inbox")
|
||||
}
|
||||
|
||||
function Get-RalphProcessingDirectory {
|
||||
return (Join-Path (Get-RalphRoot) "tasks\processing")
|
||||
}
|
||||
|
||||
function Get-RalphArchiveDirectory {
|
||||
return (Join-Path (Get-RalphRoot) "tasks\completed")
|
||||
}
|
||||
|
||||
function Get-RalphFailedDirectory {
|
||||
return (Join-Path (Get-RalphRoot) "tasks\failed")
|
||||
}
|
||||
|
||||
function Read-TaskPack {
|
||||
param([string]$TaskDirectory = $(Join-Path (Get-RalphRoot) "tasks\current"))
|
||||
|
||||
$files = Get-TaskFiles -TaskDirectory $TaskDirectory
|
||||
foreach ($entry in $files.GetEnumerator()) {
|
||||
if (-not (Test-Path $entry.Value)) {
|
||||
throw "Task pack file missing: $($entry.Value)"
|
||||
}
|
||||
}
|
||||
|
||||
return @{
|
||||
Files = $files
|
||||
Task = Get-Content -Raw -Path $files.Task
|
||||
Acceptance = Get-Content -Raw -Path $files.Acceptance
|
||||
Context = Get-Content -Raw -Path $files.Context
|
||||
}
|
||||
}
|
||||
|
||||
function Convert-MarkdownToRalphTaskPack {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path
|
||||
)
|
||||
|
||||
if (-not (Test-Path $Path)) {
|
||||
throw "Task markdown not found: $Path"
|
||||
}
|
||||
|
||||
$raw = Get-Content -Raw -Path $Path
|
||||
$lines = $raw -split "`r?`n"
|
||||
$taskLines = New-Object System.Collections.Generic.List[string]
|
||||
$acceptanceLines = New-Object System.Collections.Generic.List[string]
|
||||
$contextLines = New-Object System.Collections.Generic.List[string]
|
||||
$current = "task"
|
||||
$title = ""
|
||||
|
||||
foreach ($line in $lines) {
|
||||
if ($line -match '^\s*#{1,3}\s+(.*\S)\s*$') {
|
||||
$heading = $matches[1].Trim()
|
||||
if (-not $title -and $line -match '^\s*#\s+') {
|
||||
$title = $heading
|
||||
}
|
||||
|
||||
$headingLower = $heading.ToLowerInvariant()
|
||||
if ($headingLower -match '^(acceptance|acceptance criteria|criteria|done|definition of done)\b') {
|
||||
$current = "acceptance"
|
||||
}
|
||||
elseif ($headingLower -match '^(context|background|notes|references)\b') {
|
||||
$current = "context"
|
||||
}
|
||||
else {
|
||||
$current = "task"
|
||||
$taskLines.Add($line)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch ($current) {
|
||||
"acceptance" { $acceptanceLines.Add($line) }
|
||||
"context" { $contextLines.Add($line) }
|
||||
default { $taskLines.Add($line) }
|
||||
}
|
||||
}
|
||||
|
||||
$taskText = (($taskLines -join "`n").Trim())
|
||||
$acceptanceText = (($acceptanceLines -join "`n").Trim())
|
||||
$contextText = (($contextLines -join "`n").Trim())
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($taskText)) {
|
||||
$taskText = $raw.Trim()
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($acceptanceText)) {
|
||||
$acceptanceText = Get-Content -Raw -Path (Join-Path (Get-RalphRoot) "templates\ACCEPTANCE.md")
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($contextText)) {
|
||||
$contextText = Get-Content -Raw -Path (Join-Path (Get-RalphRoot) "templates\CONTEXT.md")
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($title)) {
|
||||
$title = [IO.Path]::GetFileNameWithoutExtension($Path)
|
||||
}
|
||||
|
||||
return [ordered]@{
|
||||
Title = $title
|
||||
SourcePath = $Path
|
||||
RawContent = $raw
|
||||
Task = $taskText
|
||||
Acceptance = $acceptanceText
|
||||
Context = $contextText
|
||||
}
|
||||
}
|
||||
|
||||
function Write-RalphTaskPackDirectory {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][hashtable]$TaskPack,
|
||||
[Parameter(Mandatory = $true)][string]$DestinationDirectory
|
||||
)
|
||||
|
||||
Ensure-Directory -Path $DestinationDirectory
|
||||
Write-Utf8File -Path (Join-Path $DestinationDirectory "TASK.md") -Content (($TaskPack.Task.Trim()) + "`n")
|
||||
Write-Utf8File -Path (Join-Path $DestinationDirectory "ACCEPTANCE.md") -Content (($TaskPack.Acceptance.Trim()) + "`n")
|
||||
Write-Utf8File -Path (Join-Path $DestinationDirectory "CONTEXT.md") -Content (($TaskPack.Context.Trim()) + "`n")
|
||||
|
||||
if ($TaskPack.ContainsKey("SourcePath") -and (Test-Path $TaskPack.SourcePath)) {
|
||||
Copy-Item -Path $TaskPack.SourcePath -Destination (Join-Path $DestinationDirectory "SOURCE.md") -Force
|
||||
}
|
||||
}
|
||||
|
||||
function Write-Utf8File {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path,
|
||||
[Parameter(Mandatory = $true)][AllowEmptyString()][string]$Content
|
||||
)
|
||||
|
||||
$encoding = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText($Path, $Content, $encoding)
|
||||
}
|
||||
|
||||
function Write-JsonFile {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Path,
|
||||
[Parameter(Mandatory = $true)]$Object
|
||||
)
|
||||
|
||||
$json = $Object | ConvertTo-Json -Depth 100
|
||||
Write-Utf8File -Path $Path -Content ($json + "`n")
|
||||
}
|
||||
|
||||
function Get-RalphStateFile {
|
||||
param([Parameter(Mandatory = $true)][string]$Name)
|
||||
return Join-Path (Get-RalphRoot) ("state\" + $Name)
|
||||
}
|
||||
|
||||
function Get-RalphTaskRoots {
|
||||
$ralphRoot = Get-RalphRoot
|
||||
return [ordered]@{
|
||||
TasksRoot = Join-Path $ralphRoot "tasks"
|
||||
Inbox = Join-Path $ralphRoot "tasks\inbox"
|
||||
Processing = Join-Path $ralphRoot "tasks\processing"
|
||||
Completed = Join-Path $ralphRoot "tasks\completed"
|
||||
Failed = Join-Path $ralphRoot "tasks\failed"
|
||||
}
|
||||
}
|
||||
|
||||
function Convert-ToRalphSlug {
|
||||
param([Parameter(Mandatory = $true)][string]$Text)
|
||||
|
||||
$slug = [string]$Text
|
||||
$slug = $slug.Trim().ToLowerInvariant()
|
||||
if ([string]::IsNullOrWhiteSpace($slug)) {
|
||||
return "task"
|
||||
}
|
||||
|
||||
$slug = [regex]::Replace($slug, "[^a-z0-9]+", "-")
|
||||
$slug = $slug.Trim("-")
|
||||
if ([string]::IsNullOrWhiteSpace($slug)) {
|
||||
return "task"
|
||||
}
|
||||
if ($slug.Length -gt 48) {
|
||||
$slug = $slug.Substring(0, 48).Trim("-")
|
||||
}
|
||||
return $slug
|
||||
}
|
||||
|
||||
function Get-RalphTaskTitleFromMarkdown {
|
||||
param(
|
||||
[string]$Markdown = "",
|
||||
[string]$Fallback = "task"
|
||||
)
|
||||
|
||||
$lines = @($Markdown -split "`r?`n")
|
||||
foreach ($line in $lines) {
|
||||
$trimmed = [string]$line
|
||||
$trimmed = $trimmed.Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($trimmed)) {
|
||||
continue
|
||||
}
|
||||
if ($trimmed.StartsWith("#")) {
|
||||
return ($trimmed.TrimStart("#").Trim())
|
||||
}
|
||||
return $trimmed
|
||||
}
|
||||
return $Fallback
|
||||
}
|
||||
|
||||
function New-RalphTaskPackFromMarkdown {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$MarkdownPath,
|
||||
[string]$TargetDirectory = "",
|
||||
[string]$TaskId = "",
|
||||
[string]$Title = "",
|
||||
[hashtable]$AdditionalMetadata = @{}
|
||||
)
|
||||
|
||||
if (-not (Test-Path $MarkdownPath)) {
|
||||
throw "Markdown source not found: $MarkdownPath"
|
||||
}
|
||||
|
||||
$roots = Get-RalphTaskRoots
|
||||
foreach ($path in $roots.Values) {
|
||||
Ensure-Directory -Path $path
|
||||
}
|
||||
|
||||
$parsedTaskPack = Convert-MarkdownToRalphTaskPack -Path $MarkdownPath
|
||||
$sourceText = [string]$parsedTaskPack.RawContent
|
||||
$sourceName = [System.IO.Path]::GetFileNameWithoutExtension($MarkdownPath)
|
||||
if ([string]::IsNullOrWhiteSpace($Title)) {
|
||||
$Title = [string]$parsedTaskPack.Title
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($TaskId)) {
|
||||
$TaskId = "{0}-{1}" -f (Get-Date -Format "yyyyMMdd-HHmmss"), (Convert-ToRalphSlug -Text $Title)
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($TargetDirectory)) {
|
||||
$TargetDirectory = Join-Path $roots.Inbox $TaskId
|
||||
}
|
||||
|
||||
Ensure-Directory -Path $TargetDirectory
|
||||
|
||||
$taskPath = Join-Path $TargetDirectory "TASK.md"
|
||||
$acceptancePath = Join-Path $TargetDirectory "ACCEPTANCE.md"
|
||||
$contextPath = Join-Path $TargetDirectory "CONTEXT.md"
|
||||
$metadataPath = Join-Path $TargetDirectory "submission.json"
|
||||
$sourceCopyPath = Join-Path $TargetDirectory "SOURCE.md"
|
||||
|
||||
$acceptance = [string]$parsedTaskPack.Acceptance
|
||||
$context = [string]$parsedTaskPack.Context
|
||||
$taskBody = [string]$parsedTaskPack.Task
|
||||
|
||||
Write-Utf8File -Path $taskPath -Content ($taskBody.Trim() + "`n")
|
||||
Write-Utf8File -Path $sourceCopyPath -Content ($sourceText.Trim() + "`n")
|
||||
Write-Utf8File -Path $acceptancePath -Content ($acceptance + "`n")
|
||||
Write-Utf8File -Path $contextPath -Content ($context + "`n")
|
||||
$metadata = [ordered]@{
|
||||
id = $TaskId
|
||||
title = $Title
|
||||
submitted_at = (Get-Date).ToString("o")
|
||||
source_path = (Resolve-Path $MarkdownPath).Path
|
||||
task_directory = $TargetDirectory
|
||||
state = "queued"
|
||||
}
|
||||
foreach ($key in $AdditionalMetadata.Keys) {
|
||||
$metadata[$key] = $AdditionalMetadata[$key]
|
||||
}
|
||||
Write-JsonFile -Path $metadataPath -Object $metadata
|
||||
|
||||
return [ordered]@{
|
||||
id = $TaskId
|
||||
title = $Title
|
||||
task_directory = $TargetDirectory
|
||||
task_file = $taskPath
|
||||
acceptance_file = $acceptancePath
|
||||
context_file = $contextPath
|
||||
metadata_file = $metadataPath
|
||||
source_copy = $sourceCopyPath
|
||||
}
|
||||
}
|
||||
|
||||
function Read-CodexReviewVerdict {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
|
||||
if (-not (Test-Path $Path)) {
|
||||
throw "Codex review file not found: $Path"
|
||||
}
|
||||
|
||||
$raw = (Get-Content -Raw -Path $Path).Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($raw)) {
|
||||
throw "Codex review file is empty: $Path"
|
||||
}
|
||||
|
||||
$jsonText = $raw
|
||||
if ($raw -match '(?s)```json\s*(\{.*?\})\s*```') {
|
||||
$jsonText = $matches[1]
|
||||
}
|
||||
elseif ($raw -match '(?s)(\{.*\})') {
|
||||
$jsonText = $matches[1]
|
||||
}
|
||||
|
||||
try {
|
||||
$parsed = $jsonText | ConvertFrom-Json
|
||||
}
|
||||
catch {
|
||||
throw "Codex review output is not valid JSON: $Path"
|
||||
}
|
||||
|
||||
$verdict = [string]$parsed.verdict
|
||||
if ([string]::IsNullOrWhiteSpace($verdict)) {
|
||||
throw "Codex review JSON missing 'verdict': $Path"
|
||||
}
|
||||
|
||||
$normalizedVerdict = $verdict.Trim().ToLowerInvariant()
|
||||
if ($normalizedVerdict -notin @("pass", "needs_fix", "fail")) {
|
||||
throw "Codex review verdict '$verdict' is invalid. Expected: pass, needs_fix, fail."
|
||||
}
|
||||
|
||||
$acceptancePassed = $false
|
||||
try {
|
||||
$acceptancePassed = [bool]$parsed.acceptance_passed
|
||||
}
|
||||
catch {
|
||||
$acceptancePassed = ($normalizedVerdict -eq "pass")
|
||||
}
|
||||
|
||||
return [ordered]@{
|
||||
verdict = $normalizedVerdict
|
||||
acceptance_passed = $acceptancePassed
|
||||
fix_required = [bool]$parsed.fix_required
|
||||
summary = [string]$parsed.summary
|
||||
next_sprint_needed = [bool]$parsed.next_sprint_needed
|
||||
next_sprint_brief = [string]$parsed.next_sprint_brief
|
||||
highest_risk_issues = @($parsed.highest_risk_issues)
|
||||
raw = $parsed
|
||||
}
|
||||
}
|
||||
|
||||
function Add-RalphEvent {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$RunId,
|
||||
[Parameter(Mandatory = $true)][string]$Stage,
|
||||
[Parameter(Mandatory = $true)][string]$Status,
|
||||
[Parameter(Mandatory = $true)][string]$Message,
|
||||
[string]$Actor = "system",
|
||||
[hashtable]$Data = @{}
|
||||
)
|
||||
|
||||
$eventsPath = Get-RalphStateFile -Name "events.jsonl"
|
||||
Ensure-Directory -Path (Split-Path -Parent $eventsPath)
|
||||
|
||||
$event = [ordered]@{
|
||||
timestamp = (Get-Date).ToString("o")
|
||||
run_id = $RunId
|
||||
actor = $Actor
|
||||
stage = $Stage
|
||||
status = $Status
|
||||
message = $Message
|
||||
data = $Data
|
||||
}
|
||||
|
||||
$encoding = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::AppendAllText($eventsPath, (($event | ConvertTo-Json -Depth 20 -Compress) + "`n"), $encoding)
|
||||
}
|
||||
|
||||
function Set-RalphCurrentRunState {
|
||||
param([Parameter(Mandatory = $true)][hashtable]$State)
|
||||
$statePath = Get-RalphStateFile -Name "current_run.json"
|
||||
Write-JsonFile -Path $statePath -Object $State
|
||||
}
|
||||
|
||||
function New-PromptDocument {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$TemplatePath,
|
||||
[Parameter(Mandatory = $true)][hashtable]$TaskPack,
|
||||
[Parameter(Mandatory = $true)][string]$OutputPath,
|
||||
[string[]]$ExtraSections = @()
|
||||
)
|
||||
|
||||
$template = Get-Content -Raw -Path $TemplatePath
|
||||
$sections = @(
|
||||
$template,
|
||||
"## TASK`n$($TaskPack.Task)",
|
||||
"## ACCEPTANCE`n$($TaskPack.Acceptance)",
|
||||
"## CONTEXT`n$($TaskPack.Context)"
|
||||
) + $ExtraSections
|
||||
|
||||
Write-Utf8File -Path $OutputPath -Content (($sections -join "`n`n").Trim() + "`n")
|
||||
}
|
||||
Reference in New Issue
Block a user