param( [string]$BackendBaseUrl = "http://192.168.3.90:8001", [int]$FrontendPort = 3004, [switch]$SkipBaseline ) $ErrorActionPreference = "Stop" $repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") $frontendDir = Join-Path $repoRoot "frontend" $backendDir = Join-Path $repoRoot "backend" $timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss" $artifactDir = Join-Path $repoRoot "project-docs\qa-artifacts\phase1-closed-loop\$timestamp" $serverLog = Join-Path $artifactDir "frontend-dev-server.log" $webUrl = "http://127.0.0.1:$FrontendPort" New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) { throw "gstack is required for all AI-assisted work in this repo." } function Wait-HttpReady { param( [string]$Url, [int]$TimeoutSeconds = 90 ) $deadline = (Get-Date).AddSeconds($TimeoutSeconds) do { try { $response = Invoke-WebRequest -Uri $Url -TimeoutSec 3 if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) { return } } catch { Start-Sleep -Seconds 2 } } while ((Get-Date) -lt $deadline) throw "Timed out waiting for $Url" } function Stop-Listener { param([int]$Port) $listener = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue | Where-Object { $_.State -eq "Listen" } | Select-Object -First 1 if ($listener) { Stop-Process -Id $listener.OwningProcess -Force -ErrorAction SilentlyContinue } } function Stop-ExistingNextDevProcess { param([string]$TargetDirectory) $normalizedTarget = $TargetDirectory.ToLowerInvariant() $processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq "node.exe" -and $_.CommandLine -and $_.CommandLine.ToLowerInvariant().Contains($normalizedTarget) -and ( $_.CommandLine -match "next\\dist\\bin\\next" -or $_.CommandLine -match "start-server\.js" ) } foreach ($process in $processes) { Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue } } $frontendProcess = $null try { if (-not (Test-Path (Join-Path $frontendDir "node_modules"))) { Push-Location $frontendDir npm.cmd install --no-package-lock if ($LASTEXITCODE -ne 0) { throw "npm install failed" } Pop-Location } if (-not (Test-Path (Join-Path $frontendDir "node_modules\playwright"))) { Push-Location $frontendDir npm.cmd install --no-save --no-package-lock playwright if ($LASTEXITCODE -ne 0) { throw "playwright package install failed" } Pop-Location } Push-Location $frontendDir npx.cmd playwright install chromium if ($LASTEXITCODE -ne 0) { throw "playwright browser install failed" } Pop-Location if (-not $SkipBaseline) { Push-Location $frontendDir npm.cmd run lint if ($LASTEXITCODE -ne 0) { throw "frontend lint failed" } npm.cmd run build if ($LASTEXITCODE -ne 0) { throw "frontend build failed" } Pop-Location Push-Location $repoRoot .\backend\.venv\Scripts\python.exe -m pytest -q ` backend/tests/test_auth_api.py ` backend/tests/test_auth_service.py ` backend/tests/test_chat_api.py ` backend/tests/test_contracts_api.py ` backend/tests/test_files_api.py ` backend/tests/test_knowledge_bases_api.py ` backend/tests/test_review_rules_api.py ` backend/tests/test_review_rule_service.py ` backend/tests/test_review_service.py ` backend/tests/test_review_service_rules.py ` backend/tests/test_reviews_api.py ` backend/tests/test_templates_api.py ` backend/tests/test_users_api.py if ($LASTEXITCODE -ne 0) { throw "backend pytest failed" } Pop-Location } Stop-ExistingNextDevProcess -TargetDirectory $frontendDir Stop-Listener -Port $FrontendPort $serverCommand = "`$env:NEXT_PUBLIC_API_BASE_URL='/api/v1'; `$env:BACKEND_BASE_URL='$BackendBaseUrl'; npm.cmd run dev -- --hostname 127.0.0.1 --port $FrontendPort *> '$serverLog'" $frontendProcess = Start-Process -FilePath "powershell.exe" ` -ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $serverCommand) ` -WorkingDirectory $frontendDir ` -WindowStyle Hidden ` -PassThru Wait-HttpReady -Url "$webUrl/login" -TimeoutSeconds 120 Wait-HttpReady -Url "$BackendBaseUrl/health" -TimeoutSeconds 30 Push-Location $frontendDir $env:PHASE1_WEB_URL = $webUrl $env:PHASE1_API_URL = "$BackendBaseUrl/api/v1" $env:PHASE1_HEALTH_URL = "$BackendBaseUrl/health" $env:PHASE1_ARTIFACT_DIR = $artifactDir node .\scripts\phase1-closed-loop-e2e.mjs if ($LASTEXITCODE -ne 0) { throw "phase1 closed-loop e2e failed" } Pop-Location Write-Host "PHASE1_CLOSED_LOOP_PASS $artifactDir" } finally { if ($frontendProcess -and -not $frontendProcess.HasExited) { Stop-Process -Id $frontendProcess.Id -Force -ErrorAction SilentlyContinue } Stop-Listener -Port $FrontendPort }