From 28a2e42c41de1de245ed23062811c9ac1049264b Mon Sep 17 00:00:00 2001 From: Rijad Zuzo Date: Sun, 5 Jul 2026 23:06:18 +0200 Subject: [PATCH] feat: add local quality gate scripts and documentation Add quality.sh (bash), quality.ps1 (PowerShell), and fix_format.sh for automated GDScript quality checking. Includes: - gdformat --check (formatting gate) - gdlint (lint rules) - godot --headless project validation (with 30s timeout) - Optional GUT test support - --changed mode for checking only modified .gd files - Compact summary output with NEXT FIX suggestions - Full logs saved to logs/quality/latest/ Add docs/local_quality_gate.md with setup and usage instructions. --- .gitignore | 3 + docs/local_quality_gate.md | 106 ++++++++++++++ tools/fix_format.sh | 24 +++ tools/quality.ps1 | 293 +++++++++++++++++++++++++++++++++++++ tools/quality.sh | 242 ++++++++++++++++++++++++++++++ 5 files changed, 668 insertions(+) create mode 100644 docs/local_quality_gate.md create mode 100644 tools/fix_format.sh create mode 100644 tools/quality.ps1 create mode 100644 tools/quality.sh diff --git a/.gitignore b/.gitignore index 373815a..a3dd3fd 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,9 @@ ehthumbs_vista.db $RECYCLE.BIN/ *.stackdump +# Local quality gate logs +logs/quality/ + # Linux desktop and trash metadata .directory .Trash-* diff --git a/docs/local_quality_gate.md b/docs/local_quality_gate.md new file mode 100644 index 0000000..86229dd --- /dev/null +++ b/docs/local_quality_gate.md @@ -0,0 +1,106 @@ +# Local Quality Gate + +A single command to detect broken GDScript, formatting issues, lint problems, +parser errors, and LLM-generated garbage before committing. + +## Requirements + +- **Python 3** with `gdtoolkit` (gdscript-toolkit): + ```bash + pip install gdtoolkit + ``` +- **Godot 4** binary on `PATH`, or set the `GODOT_BIN` environment variable + +## Usage + +### Full project check + +```bash +# Linux / macOS / Git Bash +./tools/quality.sh + +# Windows PowerShell +pwsh ./tools/quality.ps1 +``` + +### Fast changed-files mode + +Only runs `gdformat` / `gdlint` on `.gd` files modified since the last commit. +The Godot parser check still runs on the whole project. + +```bash +./tools/quality.sh --changed +pwsh ./tools/quality.ps1 -Changed +``` + +### Auto-format (not part of the gate) + +```bash +./tools/fix_format.sh +``` + +## What it checks + +| Check | Tool | What it detects | +|---|---|---| +| **gdformat** | `gdformat --check .` | Unformatted code (indentation, spacing) | +| **gdlint** | `gdlint .` | Lint violations (unused args, long lines, naming, etc.) | +| **godot-check** | `godot --headless --check-only` | Parser/syntax errors, missing dependencies | +| **gut** | `godot -s gut_cmdln.gd` | Failing GUT unit tests (only if addon exists) | + +## Output + +If everything passes: + +``` +QUALITY RESULT: PASS + + gdformat PASS + gdlint PASS + godot-check PASS + gut SKIPPED + +Full logs: + logs/quality/latest/ +``` + +On failure, only relevant errors and fix suggestions are shown: + +``` +QUALITY RESULT: FAIL + + gdformat FAIL + gdlint FAIL + godot-check PASS + gut SKIPPED + + res://simulation/SimNPC.gd:42 Unused argument 'delta' + res://simulation/task_manager.gd:88 Line too long (120 > 100) + + NEXT FIX: + 1. Fix unused-argument in SimNPC.gd + 2. Run gdformat to auto-format files + +Full logs: + logs/quality/latest/ +``` + +Full logs are always written to `logs/quality/latest/` and never dumped +to the terminal. + +## Environment variables + +| Variable | Description | +|---|---| +| `GODOT_BIN` | Path to the Godot executable (auto-detected from PATH if unset) | + +## CI setup + +To add this to CI, install the dependencies and run: + +```bash +pip install gdtoolkit +./tools/quality.sh +``` + +The script exits non-zero on any failure, so it will fail the CI step. diff --git a/tools/fix_format.sh b/tools/fix_format.sh new file mode 100644 index 0000000..885eb8b --- /dev/null +++ b/tools/fix_format.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# fix_format.sh — Auto-format all GDScript files with gdformat. +# Usage: ./tools/fix_format.sh +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +# detect gdformat +FMT="" +if command -v gdformat &>/dev/null; then + FMT="gdformat" +elif python -m gdtoolkit.formatter --help &>/dev/null 2>&1; then + FMT="python -m gdtoolkit.formatter" +fi + +if [[ -z "$FMT" ]]; then + echo "ERROR: gdformat not found. Install: pip install gdtoolkit" + exit 1 +fi + +echo "Formatting all .gd files in $ROOT ..." +$FMT . +echo "Done." diff --git a/tools/quality.ps1 b/tools/quality.ps1 new file mode 100644 index 0000000..c169964 --- /dev/null +++ b/tools/quality.ps1 @@ -0,0 +1,293 @@ +#!/usr/bin/env pwsh +# quality.ps1 — Local quality gate for GDScript projects (Windows). +# Usage: pwsh ./tools/quality.ps1 [-Changed] [--changed] +param([switch]$Changed) +if ($args -contains '--changed') { $Changed = $true } + +$ROOT = Split-Path -Parent $PSScriptRoot +Set-Location $ROOT + +$LOG = "logs/quality/latest" +New-Item -ItemType Directory -Path $LOG -Force | Out-Null + +$OVERALL = $false +$ERRORS = @() +$FIXES = @() + +# -- tool detection ----------------------------------------------------------- +function Find-Godot { + if ($env:GODOT_BIN) { return $env:GODOT_BIN } + try { return (Get-Command godot -ErrorAction Stop).Source } catch {} + try { return (Get-Command godot4 -ErrorAction Stop).Source } catch {} + $common = @( + "$env:ProgramFiles\Godot\godot.exe", + "${env:ProgramFiles(x86)}\Godot\godot.exe", + "$env:LOCALAPPDATA\Godot\godot.exe", + "$env:USERPROFILE\AppData\Local\Godot\godot.exe" + ) + foreach ($p in $common) { if (Test-Path $p) { return $p } } + return $null +} + +function Test-GdFormat { + if (Get-Command gdformat -ErrorAction SilentlyContinue) { return $true } + try { + $null = & python -m gdtoolkit.formatter --help 2>&1 + if ($LASTEXITCODE -eq 0) { return $true } + } catch {} + try { + $null = & python3 -m gdtoolkit.formatter --help 2>&1 + if ($LASTEXITCODE -eq 0) { return $true } + } catch {} + return $false +} + +function Test-GdLint { + if (Get-Command gdlint -ErrorAction SilentlyContinue) { return $true } + try { + $null = & python -m gdtoolkit.linter --help 2>&1 + if ($LASTEXITCODE -eq 0) { return $true } + } catch {} + try { + $null = & python3 -m gdtoolkit.linter --help 2>&1 + if ($LASTEXITCODE -eq 0) { return $true } + } catch {} + return $false +} + +function Invoke-GdFormat { + $args = $args + if (Get-Command gdformat -ErrorAction SilentlyContinue) { + & gdformat @args 2>&1 + } else { + & python -m gdtoolkit.formatter @args 2>&1 + } +} + +function Invoke-GdLint { + $args = $args + if (Get-Command gdlint -ErrorAction SilentlyContinue) { + & gdlint @args 2>&1 + } else { + & python -m gdtoolkit.linter @args 2>&1 + } +} + +function Get-ChangedGd { + $f = git diff --name-only HEAD -- '*.gd' 2>$null + return @($f | Where-Object { $_ -ne '' }) +} + +function To-ResPath($p) { + $p = $p.Replace('\', '/') + if ($p.StartsWith($ROOT)) { $p = $p.Substring($ROOT.Length + 1) } + if ($p.StartsWith('./')) { $p = $p.Substring(2) } + return "res://$p" +} + +function Invoke-GodotWithTimeout { + param([string]$Args) + $psi = New-Object Diagnostics.ProcessStartInfo + $psi.FileName = $GODOT + $psi.Arguments = $Args + $psi.RedirectStandardOutput = $true + $psi.RedirectStandardError = $true + $psi.UseShellExecute = $false + $psi.CreateNoWindow = $true + $p = [Diagnostics.Process]::Start($psi) + $exited = $p.WaitForExit(30000) + if (-not $exited) { + $p.Kill() + $output = $p.StandardOutput.ReadToEnd() + $err = $p.StandardError.ReadToEnd() + return $output + $err, $null + } + $output = $p.StandardOutput.ReadToEnd() + $err = $p.StandardError.ReadToEnd() + return ($output + $err), $p.ExitCode +} + +# -- godot -------------------------------------------------------------------- +$GODOT = Find-Godot +if (-not $GODOT) { + Write-Host "ERROR: Godot binary not found. Set GODOT_BIN or add godot/godot4 to PATH." + exit 1 +} + +# -- 1. gdformat -------------------------------------------------------------- +$fmtAvail = Test-GdFormat +$fmtResult = "PASS" +Set-Content -Path "$LOG/gdformat.log" -Value "" -Encoding utf8 +if (-not $fmtAvail) { + $fmtResult = "SKIPPED" + Add-Content -Path "$LOG/gdformat.log" -Value "gdformat not found -- install: pip install gdtoolkit" -Encoding utf8 +} else { + if ($Changed) { + $files = Get-ChangedGd + if ($files.Count -eq 0) { + Add-Content -Path "$LOG/gdformat.log" -Value "No changed .gd files to check." -Encoding utf8 + } else { + $total = 0; $bad = 0 + foreach ($f in $files) { + if (-not (Test-Path $f)) { continue } + $total++ + $out = Invoke-GdFormat "--check" $f + $out | Add-Content -Path "$LOG/gdformat.log" -Encoding utf8 + if ($LASTEXITCODE -ne 0) { $bad++ } + } + $msg = "($bad/$total files need formatting)" + Add-Content -Path "$LOG/gdformat.log" -Value $msg -Encoding utf8 + } + } else { + $out = Invoke-GdFormat "--check" "." + $out | Add-Content -Path "$LOG/gdformat.log" -Encoding utf8 + } +} + +if (Select-String -SimpleMatch "would reformat" -Path "$LOG/gdformat.log" -Quiet) { + $fmtResult = "FAIL" + $lines = Select-String -SimpleMatch "would reformat" -Path "$LOG/gdformat.log" | Select-Object -ExpandProperty Line + foreach ($line in $lines) { + $p = ($line -replace '^would reformat ', '').Trim() + $rp = To-ResPath $p + $ERRORS += "$rp needs formatting" + } + $FIXES += "Run gdformat to auto-format files" +} + +# -- 2. gdlint ---------------------------------------------------------------- +$lintAvail = Test-GdLint +$lintResult = "PASS" +Set-Content -Path "$LOG/gdlint.log" -Value "" -Encoding utf8 +if (-not $lintAvail) { + $lintResult = "SKIPPED" + Add-Content -Path "$LOG/gdlint.log" -Value "gdlint not found -- install: pip install gdtoolkit" -Encoding utf8 +} else { + if ($Changed) { + $files = Get-ChangedGd + if ($files.Count -eq 0) { + Add-Content -Path "$LOG/gdlint.log" -Value "No changed .gd files to lint." -Encoding utf8 + } else { + foreach ($f in $files) { + if (-not (Test-Path $f)) { continue } + $out = Invoke-GdLint $f + $out | Add-Content -Path "$LOG/gdlint.log" -Encoding utf8 + } + } + } else { + $out = Invoke-GdLint "." + $out | Add-Content -Path "$LOG/gdlint.log" -Encoding utf8 + } +} + +$lintLines = Select-String -Pattern '^[^ ]+:\d+:\d+:' -Path "$LOG/gdlint.log" | Select-Object -ExpandProperty Line +if ($lintLines) { + $lintResult = "FAIL" + foreach ($line in $lintLines) { + $parts = $line -split ':', 4 + if ($parts.Count -lt 4) { continue } + $file = $parts[0]; $lineno = $parts[1]; $msg = $parts[3] + $rp = To-ResPath $file + $ERRORS += "$rp`:$lineno $msg" + } + foreach ($line in $lintLines) { + $parts = $line -split ':', 5 + if ($parts.Count -lt 5) { continue } + $rule = $parts[3]; $fname = Split-Path -Leaf $parts[0] + $FIXES += "Fix $rule in $fname" + } +} + +# -- 3. godot headless check -------------------------------------------------- +$godotResult = "PASS" +Set-Content -Path "$LOG/godot-check.log" -Value "" -Encoding utf8 +$godotArgs = "--headless --path `"$ROOT`" --quit" +$godotOutput, $godotExit = Invoke-GodotWithTimeout -Args $godotArgs +$godotOutput | Out-File -FilePath "$LOG/godot-check.log" -Encoding utf8 +if ($godotExit -eq $null) { + $godotResult = "PASS" + Add-Content -Path "$LOG/godot-check.log" -Value "[timed out after 30s - project runs continuously]" -Encoding utf8 +} elseif ($godotExit -eq 0) { + $godotResult = "PASS" +} else { + $godotResult = "FAIL" + $godotOutput | Select-String -Pattern 'error|warning|parse|syntax' | Select-Object -First 20 | ForEach-Object { + $ERRORS += "godot: $_" + } + $FIXES += "Fix Godot parser errors" +} + +# -- 4. GUT (optional) -------------------------------------------------------- +$gutResult = "SKIPPED" +Set-Content -Path "$LOG/gut.log" -Value "" -Encoding utf8 +if (Test-Path "addons/gut/gut_cmdln.gd") { + $gutArgs = "--headless --path `"$ROOT`" -s addons/gut/gut_cmdln.gd --quit" + $gutOutput, $gutExit = Invoke-GodotWithTimeout -Args $gutArgs + $gutOutput | Out-File -FilePath "$LOG/gut.log" -Encoding utf8 + if ($gutExit -eq $null) { + $gutResult = "FAIL" + Add-Content -Path "$LOG/gut.log" -Value "[timed out after 30s]" -Encoding utf8 + $ERRORS += "gut: GUT test timed out after 30 seconds" + $FIXES += "Fix GUT tests (timeout)" + } elseif ($gutExit -eq 0) { + $gutResult = "PASS" + } else { + $gutResult = "FAIL" + $gutOutput | Select-String -Pattern 'fail|error|assert' | Select-Object -First 20 | ForEach-Object { + $ERRORS += "gut: $_" + } + $FIXES += "Fix failing GUT tests" + } +} + +# -- summary ------------------------------------------------------------------ +if ($fmtResult -eq 'FAIL' -or $lintResult -eq 'FAIL' -or $godotResult -eq 'FAIL' -or $gutResult -eq 'FAIL') { + $OVERALL = $true +} + +Write-Host "" +if ($OVERALL) { Write-Host "QUALITY RESULT: FAIL" } else { Write-Host "QUALITY RESULT: PASS" } +Write-Host "" +Write-Host " gdformat $fmtResult" +Write-Host " gdlint $lintResult" +Write-Host " godot-check $godotResult" +Write-Host " gut $gutResult" + +$seen = @{} +foreach ($e in $ERRORS) { + if ($seen.ContainsKey($e)) { continue } + $seen[$e] = $true + Write-Host " $e" +} + +if ($OVERALL -and $FIXES.Count -gt 0) { + Write-Host "" + Write-Host " NEXT FIX:" + $seenFix = @{} + $idx = 1 + foreach ($fix in $FIXES) { + if ($seenFix.ContainsKey($fix)) { continue } + $seenFix[$fix] = $true + Write-Host " $idx. $fix" + $idx++ + } +} + +Write-Host "" +Write-Host "Full logs:" +Write-Host " $LOG/" +Write-Host "" + +# save summary +if ($OVERALL) { $resultText = "FAIL" } else { $resultText = "PASS" } +$summary = @" +QUALITY RESULT: $resultText + + gdformat $fmtResult + gdlint $lintResult + godot-check $godotResult + gut $gutResult +"@ +Set-Content -Path "$LOG/summary.txt" -Value $summary -Encoding utf8 + +if ($OVERALL) { exit 1 } else { exit 0 } diff --git a/tools/quality.sh b/tools/quality.sh new file mode 100644 index 0000000..359f89d --- /dev/null +++ b/tools/quality.sh @@ -0,0 +1,242 @@ +#!/usr/bin/env bash +# quality.sh — Local quality gate for GDScript projects. +# Usage: ./tools/quality.sh [--changed] +# --changed Only check changed .gd files (gdformat/gdlint only) +set -uo pipefail +IFS=$'\n\t' + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" || exit 1 +LOG="logs/quality/latest" +mkdir -p "$LOG" + +CHANGED=false +[[ "${1:-}" == "--changed" ]] && CHANGED=true +OVERALL=false +ERRORS=() +FIXES=() + +# -- tool detection ----------------------------------------------------------- +find_godot() { + if [[ -n "${GODOT_BIN:-}" ]]; then echo "$GODOT_BIN"; return 0; fi + for c in godot godot4; do command -v "$c" &>/dev/null && echo "$c" && return 0; done + return 1 +} + +find_gdformat() { + if command -v gdformat &>/dev/null; then echo "gdformat"; return 0; fi + if python -m gdtoolkit.formatter --help &>/dev/null 2>&1; then echo "python -m gdtoolkit.formatter"; return 0; fi + return 1 +} + +find_gdlint() { + if command -v gdlint &>/dev/null; then echo "gdlint"; return 0; fi + if python -m gdtoolkit.linter --help &>/dev/null 2>&1; then echo "python -m gdtoolkit.linter"; return 0; fi + return 1 +} + +changed_gd() { + git diff --name-only HEAD -- '*.gd' 2>/dev/null || true +} + +res_path() { + local p="$1" + p="${p//\\//}" + if [[ "$p" == "$ROOT"* ]]; then p="${p#"$ROOT"/}"; fi + p="${p#./}" + echo "res://$p" +} + +contains_element() { + local e; for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done; return 1 +} + +need_timeout() { + command -v timeout &>/dev/null +} + +# -- godot -------------------------------------------------------------------- +GODOT="$(find_godot)" || true +if [[ -z "$GODOT" ]]; then + echo "ERROR: Godot binary not found. Set GODOT_BIN or add godot/godot4 to PATH." + exit 1 +fi + +# -- 1. gdformat -------------------------------------------------------------- +fmt_tool=$(find_gdformat) || true +fmt_result="PASS" +: > "$LOG/gdformat.log" +if [[ -z "$fmt_tool" ]]; then + fmt_result="SKIPPED" + echo "gdformat not found — install: pip install gdtoolkit" > "$LOG/gdformat.log" +else + if $CHANGED; then + files=$(changed_gd) + if [[ -z "$files" ]]; then + echo "No changed .gd files to check." > "$LOG/gdformat.log" + else + total=0; bad=0 + while IFS= read -r f; do + [[ -f "$f" ]] || continue + total=$((total+1)) + if ! $fmt_tool --check "$f" >> "$LOG/gdformat.log" 2>&1; then + bad=$((bad+1)) + fi + done <<< "$files" + echo "($bad/$total files need formatting)" >> "$LOG/gdformat.log" + fi + else + $fmt_tool --check . >> "$LOG/gdformat.log" 2>&1 || true + fi +fi + +if grep -q "would reformat" "$LOG/gdformat.log" 2>/dev/null; then + fmt_result="FAIL" + while IFS= read -r line; do + line="${line#would reformat }" + rp=$(res_path "$line") + ERRORS+=("$rp needs formatting") + done < <(grep "would reformat" "$LOG/gdformat.log") + FIXES+=("Run gdformat to auto-format files") +fi + +# -- 2. gdlint ---------------------------------------------------------------- +lint_tool=$(find_gdlint) || true +lint_result="PASS" +: > "$LOG/gdlint.log" +if [[ -z "$lint_tool" ]]; then + lint_result="SKIPPED" + echo "gdlint not found — install: pip install gdtoolkit" > "$LOG/gdlint.log" +else + if $CHANGED; then + files=$(changed_gd) + if [[ -z "$files" ]]; then + echo "No changed .gd files to lint." > "$LOG/gdlint.log" + else + while IFS= read -r f; do + [[ -f "$f" ]] || continue + $lint_tool "$f" >> "$LOG/gdlint.log" 2>&1 || true + done <<< "$files" + fi + else + $lint_tool . >> "$LOG/gdlint.log" 2>&1 || true + fi +fi + +if grep -qE "^[^ ]+:[0-9]+:" "$LOG/gdlint.log" 2>/dev/null; then + lint_result="FAIL" + while IFS= read -r line; do + [[ "$line" =~ ^([^:]+):([0-9]+):([0-9]+):[^:]+:(.*) ]] || continue + file="${BASH_REMATCH[1]}" + lineno="${BASH_REMATCH[2]}" + msg="${BASH_REMATCH[4]}" + msg="${msg## }" + rp=$(res_path "$file") + ERRORS+=("$rp:$lineno $msg") + done < <(grep -E "^[^ ]+:[0-9]+:" "$LOG/gdlint.log") + + while IFS= read -r line; do + [[ "$line" =~ ^([^:]+):([0-9]+):([0-9]+):([^:]+):(.*) ]] || continue + rule="${BASH_REMATCH[4]}" + fname=$(basename "${BASH_REMATCH[1]}") + FIXES+=("Fix $rule in $fname") + done < <(grep -E "^[^ ]+:[0-9]+:" "$LOG/gdlint.log") +fi + +# -- 3. godot headless check -------------------------------------------------- +godot_result="PASS" +: > "$LOG/godot-check.log" +GODOT_CMD="$GODOT --headless --path \"$ROOT\" --check-only --quit" +if need_timeout; then + if timeout 30 $GODOT_CMD >> "$LOG/godot-check.log" 2>&1; then + godot_result="PASS" + else + ec=$? + if [[ $ec -eq 124 ]]; then + godot_result="PASS" + echo "[timed out after 30s — project runs continuously]" >> "$LOG/godot-check.log" + else + godot_result="FAIL" + fi + fi +else + if $GODOT --headless --path "$ROOT" --quit >> "$LOG/godot-check.log" 2>&1; then + godot_result="PASS" + else + godot_result="FAIL" + fi +fi +if [[ "$godot_result" == "FAIL" ]]; then + grep -iE "error|warning|parse|syntax" "$LOG/godot-check.log" 2>/dev/null | head -20 | while IFS= read -r errline; do + ERRORS+=("godot: $errline") + done + FIXES+=("Fix Godot parser errors") +fi + +# -- 4. GUT (optional) -------------------------------------------------------- +gut_result="SKIPPED" +: > "$LOG/gut.log" +if [[ -f "addons/gut/gut_cmdln.gd" ]]; then + if $GODOT --headless --path "$ROOT" -s addons/gut/gut_cmdln.gd >> "$LOG/gut.log" 2>&1; then + gut_result="PASS" + else + gut_result="FAIL" + grep -iE "fail|error|assert" "$LOG/gut.log" 2>/dev/null | head -20 | while IFS= read -r errline; do + ERRORS+=("gut: $errline") + done + FIXES+=("Fix failing GUT tests") + fi +fi + +# -- summary ------------------------------------------------------------------ +if [[ "$fmt_result" == "FAIL" || "$lint_result" == "FAIL" || "$godot_result" == "FAIL" || "$gut_result" == "FAIL" ]]; then + OVERALL=true +fi + +echo "" +if $OVERALL; then + echo "QUALITY RESULT: FAIL" +else + echo "QUALITY RESULT: PASS" +fi +echo "" +echo " gdformat $fmt_result" +echo " gdlint $lint_result" +echo " godot-check $godot_result" +echo " gut $gut_result" + +seen_err=() +for e in "${ERRORS[@]}"; do + contains_element "$e" "${seen_err[@]}" && continue + seen_err+=("$e") + echo " $e" +done + +if $OVERALL && [[ ${#FIXES[@]} -gt 0 ]]; then + echo "" + echo " NEXT FIX:" + seen_fix=() + idx=1 + for fix in "${FIXES[@]}"; do + contains_element "$fix" "${seen_fix[@]}" && continue + seen_fix+=("$fix") + echo " $idx. $fix" + idx=$((idx+1)) + done +fi + +echo "" +echo "Full logs:" +echo " $LOG/" +echo "" + +{ + echo "QUALITY RESULT: $($OVERALL && echo FAIL || echo PASS)" + echo "" + echo " gdformat $fmt_result" + echo " gdlint $lint_result" + echo " godot-check $godot_result" + echo " gut $gut_result" +} > "$LOG/summary.txt" + +$OVERALL && exit 1 || exit 0