# Fake nightshift for lights-out end-to-end testing. # Mimics the real CLI's observable contract: creates .nightshift\runs\\ with notes.md, # prompt.md, nightshift.log, and end-state.json, makes a real commit on a nightshift/ # branch, then exits. param() $allArgs = @($args) New-Item -ItemType Directory -Force -Path $env:LIGHTS_OUT_ROOT | Out-Null $allArgs | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $env:LIGHTS_OUT_ROOT 'stub-args.json') -Encoding UTF8 # The prompt is the final positional argument. $prompt = $allArgs[-1] function Get-StubRunId([string]$P) { $slug = $P.ToLowerInvariant() $slug = [regex]::Replace($slug, '[^a-z0-9]+', '-') $slug = [regex]::Replace($slug, '^-+', '') $slug = [regex]::Replace($slug, '-+$', '') if ($slug.Length -gt 20) { $slug = $slug.Substring(0, 20) } $slug = [regex]::Replace($slug, '-+$', '') $sha = [System.Security.Cryptography.SHA256]::Create() try { $hex = -join ($sha.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($P)) | ForEach-Object { $_.ToString('x2') }) } finally { $sha.Dispose() } "$slug-$($hex.Substring(0,6))" } $runId = Get-StubRunId $prompt $stopWhen = '' $useWorktree = $false for ($i = 0; $i -lt $allArgs.Count - 1; $i++) { if ($allArgs[$i] -eq '--stop-when') { $stopWhen = $allArgs[$i + 1] } if ($allArgs[$i] -eq '--worktree') { $useWorktree = $true } } Write-Output "stub-nightshift: starting run $runId" $effectiveRoot = (Get-Location).Path if ($useWorktree) { $repoRoot = (git rev-parse --show-toplevel) $repoName = Split-Path -Leaf $repoRoot $repoParent = Split-Path -Parent $repoRoot for ($suffix = 0; $suffix -lt 100; $suffix++) { $actualRunId = if ($suffix -eq 0) { $runId } else { "$runId-$suffix" } $worktreePath = Join-Path $repoParent "$repoName-nightshift-worktrees\$actualRunId" git worktree add -q -b "nightshift/$actualRunId" $worktreePath HEAD 2>$null if ($LASTEXITCODE -eq 0) { $runId = $actualRunId break } } Set-Location -LiteralPath $worktreePath $effectiveRoot = $worktreePath } $runDir = Join-Path (Get-Location) ".nightshift\runs\$runId" New-Item -ItemType Directory -Force -Path $runDir | Out-Null Set-Content -LiteralPath (Join-Path $runDir 'prompt.md') -Value $prompt -Encoding UTF8 $notes = "# nightshift run: $runId`n`n## Iteration Log`n`n### Iteration 1`nSimplified the widget helper. Tests still green.`n`n### Iteration 2`nRemoved a redundant branch in index.js.`n" Set-Content -LiteralPath (Join-Path $runDir 'notes.md') -Value $notes -Encoding UTF8 '{"level":"info","message":"stub engine log"}' | Set-Content -LiteralPath (Join-Path $runDir 'nightshift.log') -Encoding UTF8 if (-not $useWorktree) { for ($suffix = 0; $suffix -lt 100; $suffix++) { $actualRunId = if ($suffix -eq 0) { $runId } else { "$runId-$suffix" } git checkout -q -b "nightshift/$actualRunId" 2>$null if ($LASTEXITCODE -eq 0) { $runId = $actualRunId break } } } 'export const widget = (x) => x + 1;' | Set-Content -LiteralPath 'index.js' git add -A git commit -q -m 'refactor: simplify widget helper' '// tidied' | Add-Content -LiteralPath 'README.md' git add -A git commit -q -m 'docs: tidy readme' @{ status = 'completed' stopCondition = $stopWhen agentError = $null iterations = 2 successCount = 2 failCount = 0 endedAt = (Get-Date).ToString('o') } | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $runDir 'end-state.json') -Encoding UTF8 Write-Output " debug log $(Join-Path $runDir 'nightshift.log')" if ($useWorktree) { Write-Output "nightshift: worktree preserved at $effectiveRoot" } Write-Output "stub-nightshift: done" exit 0