AI skills in use in my daily
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

130 lines
6.6 KiB

$ErrorActionPreference = 'Continue'
# Self-contained regression suite for the lights-out skill.
# pwsh -NoProfile -File <skill>\scripts\test\e2e.ps1
# Uses a stubbed gnhf and a sandboxed state dir + vault, so it never touches the real
# registry at ~\.scout\lights-out or the Obsidian vault.
$sand = Join-Path $env:TEMP 'lights-out-e2e'
$stubSrc = Join-Path $PSScriptRoot 'stub-gnhf.ps1'
$stub = Join-Path $sand 'bin'
$env:LIGHTS_OUT_ROOT = "$sand\state"
$env:LIGHTS_OUT_VAULT_DIR = "$sand\vault"
$scriptsDir = Split-Path -Parent $PSScriptRoot
$S = Join-Path $scriptsDir 'start-run.ps1'
$C = Join-Path $scriptsDir 'check-run.ps1'
Remove-Item -Recurse -Force $sand -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path "$sand\repo", $stub | Out-Null
# Get-Command resolves .ps1 from PATH, so the stub must be named exactly gnhf.ps1.
Copy-Item $stubSrc (Join-Path $stub 'gnhf.ps1') -Force
Push-Location "$sand\repo"
git init -q -b main
git config user.email 't@t.local'
git config user.name 'T'
'# scratch' | Set-Content README.md
'export const widget = (x) => { if (true) { return x + 1; } };' | Set-Content index.js
git add -A
git commit -q -m 'init'
Pop-Location
function Show($n) { Write-Host "`n### $n" -ForegroundColor Yellow }
$script:pass = 0
$script:fail = 0
function Check($label, $cond) {
if ($cond) { Write-Host " PASS $label" -ForegroundColor Green; $script:pass++ }
else { Write-Host " FAIL $label" -ForegroundColor Red; $script:fail++ }
}
# Load the shared lib so the suite can assert on resolution directly.
. (Join-Path $scriptsDir 'lib.ps1')
function Resolve-GnhfCommandForTest { Resolve-GnhfCommand }
Show 'TEST 0: vendored gnhf is self-contained'
$vendored = Join-Path (Split-Path -Parent $scriptsDir) 'vendor\node_modules\gnhf\dist\cli.mjs'
Check 'vendored gnhf present' (Test-Path $vendored)
$env:LIGHTS_OUT_GNHF = ''
$resolved = Resolve-GnhfCommand
Check 'resolves to vendored by default' ($resolved -and $resolved.Kind -eq 'vendored')
Check 'vendored version reported' ($resolved -and $resolved.Version)
if ($resolved) { Write-Host " -> $($resolved.Kind) v$($resolved.Version): $($resolved.Source)" }
Show 'TEST 1: empty prompt rejected'
try { & $S -RepoPath "$sand\repo" -Prompt ' ' | Out-Null; Check 'empty prompt rejected' $false }
catch { Check 'empty prompt rejected' $true }
Show 'TEST 2: dirty tree + missing verify tool (pnpm trap)'
'dirty' | Set-Content "$sand\repo\scratch.txt"
$r2 = (& $S -RepoPath "$sand\repo" -Prompt 'tidy things up' -VerifyCommand 'pnpm run test') | ConvertFrom-Json
Check 'preflight failed' (-not $r2.ok)
Check 'dirty tree reported' ([bool]($r2.problems -match 'dirty'))
Check 'missing pnpm reported' ([bool]($r2.problems -match 'pnpm'))
Remove-Item "$sand\repo\scratch.txt" -Force
Show 'TEST 3: already on a gnhf/* branch (non-TTY overwrite trap)'
Push-Location "$sand\repo"; git checkout -q -b 'gnhf/fake'; Pop-Location
$r3 = (& $S -RepoPath "$sand\repo" -Prompt 'tidy things up') | ConvertFrom-Json
Check 'gnhf-branch launch blocked' (-not $r3.ok)
Push-Location "$sand\repo"; git checkout -q main; git branch -q -D 'gnhf/fake'; Pop-Location
Show 'TEST 4: unknown agent CLI blocked'
$r4 = (& $S -RepoPath "$sand\repo" -Prompt 'tidy things up' -Agent 'notarealagent') | ConvertFrom-Json
Check 'missing agent CLI blocked' (-not $r4.ok)
Show 'TEST 5: happy path with stubbed gnhf'
$env:LIGHTS_OUT_GNHF = (Join-Path $stub 'gnhf.ps1')
Check 'stub gnhf resolves via override' ((Resolve-GnhfCommandForTest).Kind -eq 'override')
$out = & $S -RepoPath "$sand\repo" -Prompt 'reduce complexity in the widget helper' -Title 'Reduce Complexity' -StopWhen 'npm test passes and no unrelated files changed' -VerifyCommand 'git --version' -MaxIterations 5 -Mode companion -Agent 'copilot'
$res = $out | ConvertFrom-Json
Check 'launch ok' ($res.ok -eq $true)
Write-Host " run id: $($res.id) pid: $($res.workerPid)"
Show 'Waiting for detached worker...'
$final = $null
for ($i = 0; $i -lt 45; $i++) {
Start-Sleep -Seconds 2
$j = & $C -Id $res.id -Json
if ($j) { $final = ($j | ConvertFrom-Json)[0] }
if ($final -and $final.status -in @('completed', 'failed', 'orphaned')) { break }
}
Check 'worker finished' ($null -ne $final)
if ($final) {
Check 'status completed' ($final.status -eq 'completed')
Check 'two commits captured' ($final.commitCount -eq 2)
Check 'end-state parsed' ($final.endState.status -eq 'completed')
Check 'verify ran before and after' (($final.verifyExitCodeBefore -eq 0) -and ($final.verifyExitCodeAfter -eq 0))
Check 'run dir resolved' ($null -ne $final.runDir)
Check 'notes tail captured' ([bool]$final.notesTail)
Check 'gnhf provenance recorded' ($final.gnhfKind -eq 'override')
}
Show 'Stub received correct gnhf flags'
$stubArgs = Get-Content "$sand\state\stub-args.json" -Raw -ErrorAction SilentlyContinue | ConvertFrom-Json
if ($stubArgs) {
Write-Host (' ' + ($stubArgs -join ' '))
Check 'passed --stop-when' ([bool]($stubArgs -contains '--stop-when'))
Check 'passed --prevent-sleep' ([bool]($stubArgs -contains '--prevent-sleep'))
Check 'passed --max-iterations' ([bool]($stubArgs -contains '--max-iterations'))
Check 'prompt is last arg' ($stubArgs[-1] -eq 'reduce complexity in the widget helper')
}
else { Check 'stub was invoked' $false }
Show 'Vault output'
Get-ChildItem -Recurse "$sand\vault" -File -ErrorAction SilentlyContinue | ForEach-Object { ' ' + $_.FullName.Replace("$sand\vault\", '') }
$note = Get-ChildItem -Recurse -Filter 'summary.md' "$sand\vault" -ErrorAction SilentlyContinue | Select-Object -First 1
Check 'summary.md written' ($null -ne $note)
if ($note) {
$body = Get-Content $note.FullName -Raw
Check 'has lights-out-run type' ($body -match 'type: lights-out-run')
Check 'has assessment stub' ($body -match '## Assessment')
Check 'records commits' ($body -match 'refactor: simplify widget helper')
Check 'diff.patch saved' (Test-Path (Join-Path $note.DirectoryName 'diff.patch'))
Check 'notes.md copied' (Test-Path (Join-Path $note.DirectoryName 'notes.md'))
$yamlOk = (python -c "import yaml,io,sys; t=io.open(sys.argv[1],encoding='utf-8').read(); yaml.safe_load(t.split('---')[1]); print('OK')" $note.FullName 2>&1) -join ''
Check 'frontmatter is valid YAML' ($yamlOk -match 'OK')
if ($yamlOk -notmatch 'OK') { Write-Host " $yamlOk" -ForegroundColor Red }
Check 'ISO started timestamp' ($body -match '\| Started \| \d{4}-\d{2}-\d{2}T[\d:.]+[+-]\d{2}:\d{2} \|')
}
Show "RESULT: $($script:pass) passed, $($script:fail) failed"
if ($note) { Write-Host "`n--- summary.md ---`n"; Get-Content $note.FullName }
$env:LIGHTS_OUT_GNHF = ''
Remove-Item -Recurse -Force $sand -ErrorAction SilentlyContinue
if ($script:fail -gt 0) { exit 1 }