scripts/append-only-logs.ps1

<#
.SYNOPSIS
    JSON Lines append-only log primitives for Specrew lifecycle events (F-051 Iteration 2b).
#>


Set-StrictMode -Version Latest

function Add-SpecrewJsonLine {
    param(
        [Parameter(Mandatory = $true)][string]$Path,
        [Parameter(Mandatory = $true)][hashtable]$Record
    )

    $directory = Split-Path -Parent $Path
    if (-not [string]::IsNullOrWhiteSpace($directory) -and -not (Test-Path -LiteralPath $directory -PathType Container)) {
        New-Item -ItemType Directory -Path $directory -Force | Out-Null
    }

    $json = ($Record | ConvertTo-Json -Depth 20 -Compress)
    $line = $json + [Environment]::NewLine
    $utf8NoBom = [System.Text.UTF8Encoding]::new($false)
    $stream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [System.IO.FileShare]::Read)
    try {
        $bytes = $utf8NoBom.GetBytes($line)
        $stream.Write($bytes, 0, $bytes.Length)
        $stream.Flush()
    }
    finally {
        $stream.Dispose()
    }
}

function Read-SpecrewJsonLines {
    param([Parameter(Mandatory = $true)][string]$Path)

    if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return @() }

    $records = New-Object System.Collections.Generic.List[object]
    foreach ($line in Get-Content -LiteralPath $Path -Encoding UTF8) {
        if ([string]::IsNullOrWhiteSpace($line)) { continue }
        try {
            $records.Add(($line | ConvertFrom-Json -AsHashtable -Depth 20)) | Out-Null
        }
        catch {
            Write-Warning ("Specrew: skipped invalid JSON Lines record in '{0}': {1}" -f $Path, $_.Exception.Message)
        }
    }

    return $records.ToArray()
}

function Get-SpecrewLifecycleEventsPath {
    param([Parameter(Mandatory = $true)][string]$ProjectRoot)
    return (Join-Path $ProjectRoot '.squad/events/lifecycle-events.jsonl')
}

function Add-SpecrewLifecycleEvent {
    param(
        [Parameter(Mandatory = $true)][string]$ProjectRoot,
        [Parameter(Mandatory = $true)][string]$EventType,
        [Parameter(Mandatory = $true)][hashtable]$Payload,
        [AllowNull()][string]$NowUtc
    )

    if ([string]::IsNullOrWhiteSpace($NowUtc)) {
        $NowUtc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
    }

    $record = [ordered]@{
        schema     = 'v1'
        recorded_at = $NowUtc
        event_type = $EventType
        payload    = $Payload
    }
    Add-SpecrewJsonLine -Path (Get-SpecrewLifecycleEventsPath -ProjectRoot $ProjectRoot) -Record $record
}