# Static check of every .bat and .cmd under the given folders for the parenthesis bug class seen on 3 October 2026: # a bare ")" that closes nothing, or an "if (...) (" block left open. cmd.exe has no dry-parse mode, so this mirrors # what its parser does with parentheses: caret escapes (^( and ^)) are not parentheses, nothing inside double quotes # or a for /f ('...') command string counts, rem and :: lines are comments. The depth is tracked across lines; it must # never go below zero and must be zero at the end of the file. Prints file:line:message and exits 1 on any finding. # # A built-in negative test checks fixtures\bad-bare-paren.bat.txt first and refuses to pass unless it is flagged. # # powershell -NoProfile -ExecutionPolicy Bypass -File tools\ci\windows\check-bat.ps1 [-Root ] param( [string]$Root = '', [string[]]$Folders = @('proto-cuda/windows-app', 'proto-cuda/windows-miner', 'proto-cuda/windows-node', 'proving/windows-wsl2', 'relay/clients', 'packaging/windows', 'app/windows', 'proto-cuda', 'proto-opencl') ) $ErrorActionPreference = 'Stop' if (-not $Root) { $Root = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path } function Check-Batch([string]$path) { $findings = @() $depth = 0 $n = 0 foreach ($raw in [IO.File]::ReadAllLines($path)) { $n += 1 $line = $raw.TrimEnd("`r") if ($line -match '^\s*(@?rem\b|::)') { continue } $s = $line -replace '\^.', '' # caret escapes: ^( ^) ^> ^< ^& ^| $s = $s -replace '"[^"]*"', '' # double-quoted strings $s = $s -replace "'[^']*'", '' # for /f ('command') strings foreach ($ch in $s.ToCharArray()) { if ($ch -eq '(') { $depth += 1 } elseif ($ch -eq ')') { $depth -= 1 if ($depth -lt 0) { $findings += @{ line = $n; msg = "')' closes nothing (bare parenthesis)" }; $depth = 0 } } } } if ($depth -gt 0) { $findings += @{ line = $n; msg = "$depth unclosed '(' at the end of the file" } } return ,$findings } # ---- 1. the negative test ---- $fixture = Join-Path $PSScriptRoot 'fixtures\bad-bare-paren.bat.txt' $fx = Check-Batch $fixture if ($fx.Count -eq 0) { Write-Host "::error::self-test failed: $fixture was not flagged"; exit 2 } Write-Host ("self-test: fixture bad-bare-paren.bat.txt is flagged as expected at line {0}: {1}" -f $fx[0].line, $fx[0].msg) # ---- 2. every .bat and .cmd ---- $files = @() foreach ($f in $Folders) { $dir = Join-Path $Root $f if (-not (Test-Path $dir)) { Write-Host "note: no folder $f"; continue } $files += Get-ChildItem -Path $dir -Recurse -Include '*.bat', '*.cmd' -File | Where-Object { $_.FullName -notmatch '\\igneum-windows-app\\|\\build\\|\\dist\\|\\packs\\|\\emu\\|\\nvrtc\\redist\\' } } $files = $files | Sort-Object FullName -Unique $bad = 0 foreach ($file in $files) { $rel = $file.FullName.Substring($Root.Length).TrimStart('\', '/') $found = Check-Batch $file.FullName if ($found.Count -eq 0) { Write-Host "ok $rel"; continue } foreach ($x in $found) { $bad += 1 Write-Host "FAIL ${rel}:$($x.line): $($x.msg)" Write-Host "::error file=$($rel -replace '\\', '/'),line=$($x.line)::$($x.msg)" } } Write-Host ("{0} batch files checked, {1} findings" -f $files.Count, $bad) if ($bad -gt 0) { exit 1 } exit 0