Private/Initialize-RaidinessRunFolder.ps1

<#
    One folder per run.

    Before this, a checkup scattered itself: evidence in ./raidiness-data, the
    HTML wherever -OutputPath pointed, and report.md and the LLM bundle in the
    browser's download folder. Nothing tied one run together and a second run
    silently overwrote the first.

    A run folder is named for the moment it started, so runs pile up instead of
    replacing each other — which is also what the report's Progress tab needs
    to compare two runs.

    `latest.txt` holds the newest folder name. Deliberately a text file and not
    a symlink: New-Item -ItemType SymbolicLink needs administrator rights or
    developer mode on Windows, and reading a tenant must never ask for either.
#>

# psrunner-lint allow: New-Item — creates local output directories on the operator's own machine; never a tenant object
function Initialize-RaidinessRunFolder {
    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        # The folder that holds the runs (each run gets a subfolder).
        [Parameter(Mandatory = $true)]
        [string] $Path,

        # Appended to the folder name, so a folder says which tenant it is about.
        [string] $TenantName
    )

    $ErrorActionPreference = 'Stop'

    $stamp = (Get-Date).ToString('yyyy-MM-dd-HHmm')
    $suffix = ''
    if ($TenantName) {
        # A tenant name reaches us as contoso.onmicrosoft.com or a raw id; keep
        # what is safe in a folder name and drop the rest rather than failing.
        $clean = ($TenantName -replace '\.onmicrosoft\.com$', '') -replace '[^A-Za-z0-9._-]', ''
        if ($clean) { $suffix = "-$clean" }
    }

    $root = Join-Path $Path "$stamp$suffix"

    # Two runs inside the same minute must not share a folder: the second would
    # read the first one's evidence back as its own.
    $unique = $root
    $attempt = 1
    while (Test-Path -Path $unique) {
        $attempt++
        $unique = "$root-$attempt"
    }
    $root = $unique

    $data = Join-Path $root 'data'
    try {
        New-Item -ItemType Directory -Path $data -Force | Out-Null
    }
    catch {
        throw "Raidiness cannot create its run folder under '$Path'. Choose a folder your account can write to and run the command again with -Path, for example: Invoke-RaidinessCheckup -Path (Join-Path `$HOME 'Documents\Raidiness'). $($_.Exception.Message)"
    }

    Split-Path -Path $root -Leaf | Out-File -FilePath (Join-Path $Path 'latest.txt') -Encoding utf8

    [pscustomobject]@{
        PSTypeName = 'Raidiness.RunFolder'
        Root       = (Resolve-Path -Path $root).Path
        Data       = (Resolve-Path -Path $data).Path
        Log        = Join-Path $root 'raidiness.log'
        Events     = Join-Path $root 'raidiness.jsonl'
        Intake     = Join-Path $data 'raidiness-intake.json'
        Report     = Join-Path $root 'report.html'
    }
}