greyscript-linter
Static-Analyse für GreyScript-Quellen VOR greybel-Build. Erkennt fehlende //command-Marker, nicht-erlaubte Builtins (run, is_folder, str_repeat u.a.) und gibt klare Fehler/Warnings aus. Trigger bei je
SKILL.mdskills/greyscript-linter/SKILL.md
GitHubgreyscript-linter
GreyScript hat kein offizielles Lint-Tool — greybel build gibt erst nach
kompletter Transpilierung Fehler aus, oft kryptisch ("Line 0"). Dieser Linter
fängt die typischen Footguns vorher ab.
When to use
- Vor jedem
greybel buildaus GreyHack-Tool-Workflows. - Als Pre-Commit-Hook in Tool-Repos.
- Beim Übernahme alter Scripts in den Sandbox-Build (
scan-greyscript.sh).
Disallowed Builtins (greybel-js Whitelist-Gap)
Diese Builtins existieren in GreyScript-Specs, aber greybel-js lehnt sie
zur Build-Zeit ab → Build-Fehler ohne klare Position:
| Builtin | Workaround |
|---|---|
run |
Shell.launch("/bin/bash", "-c", cmd) |
is_folder |
file.is_folder (object method) |
str_repeat |
str * n oder Schleife |
chr / ord |
str(code) bzw. ASCII-Loop |
md5 |
crypto.md5(str) |
Pattern
1. Linter (scripts/lint-greyscript.py)
#!/usr/bin/env python3
"""lint-greyscript.py — Static-Lint für GreyScript (.src)"""
import re, sys
from pathlib import Path
DISALLOWED = {
"run": "Shell.launch(...)",
"is_folder": "file.is_folder(...)",
"str_repeat": "str * n",
"chr": "str(code)",
"ord": "byte via chr+ord-Loop",
"md5": "crypto.md5(...)",
"include": "Import via greybel require",
}
issues = []
for path in map(Path, sys.argv[1:]):
src = path.read_text(encoding="utf-8")
lines = src.splitlines()
# Regel 1: erste nicht-leere Zeile MUSS mit //command: ... anfangen
first = next((l for l in lines if l.strip()), "")
if not first.startswith("//command:"):
issues.append(f"{path}:1 [ERROR] Erste Zeile fehlt '//command: <name>'")
# Regel 2: jede Funktion mit "command " MUSS //command: Marker haben
if re.search(r"^\s*\w+\s+command\s*=\s*function", src, re.M):
if "//command:" not in src:
issues.append(f"{path} [ERROR] command=function ohne //command-Marker")
# Regel 3: Token-basierter Builtin-Scan
for i, line in enumerate(lines, 1):
stripped = line.split("//", 1)[0] # Kommentare ignorieren
for tok in re.findall(r"\b([A-Za-z_]\w*)\s*\(", stripped):
if tok in DISALLOWED:
issues.append(
f"{path}:{i} [ERROR] Verbotenes Builtin '{tok}' — "
f"Workaround: {DISALLOWED[tok]}"
)
# Regel 4: Warnung — split() ohne max=-1 (greybel-js safe-default)
for i, line in enumerate(lines, 1):
if re.search(r"\.split\(\s*[\"'](?!\s*\")", line):
issues.append(
f"{path}:{i} [WARN] str.split() ohne Limit kann leer liefern"
)
if issues:
print("\n".join(issues))
sys.exit(1 if any("[ERROR]" in i for i in issues) else 0)
print(f"OK: {len(sys.argv)-1} Datei(en) sauber.")
2. Pre-Build-Wrapper (scripts/build-safe.sh)
#!/usr/bin/env bash
# build-safe.sh — lintet vor jedem greybel build
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
LINT="$SCRIPT_DIR/../greyscript-linter/scripts/lint-greyscript.py"
# 1) Lint
python3 "$LINT" "$@"
# 2) nur bei OK wirklich bauen
greybel build "$@"
3. Pre-Commit-Hook
# .git/hooks/pre-commit (in Tool-Repo)
#!/usr/bin/env bash
git diff --cached --name-only -- '*.src' | \
xargs -r python3 ~/.openclaw/skills/greyscript-linter/scripts/lint-greyscript.py
Pitfalls
- ❌
runist der häufigste Build-Killer —Shell.launchstattdessen. - ❌ Kommentare mit Beispiel-Code triggern den Linter genauso; Token-Scan
ignoriert aber
//-Kommentare (siehesplit("//", 1)[0]). - ❌
str.split("a")ohnemaxist erlaubt, kann aber in Edge-Cases leer sein — Linter warnt nur, blockiert nicht. - ✅ Linter nicht in GreyHack-Spielscripts ausführen (anderer Scope) — nur
auf
*.srcfür greybel-Builds. - ✅ Eigene
DISALLOWED-Liste pflegen statt blind updaten — neue greybel-Versionen können Builtins nachpflegen.
Cron-Beispiel
# Täglich 03:00 — alle Tool-Repos linten
0 3 * * * find /home/bratan/maxclaw-tools -name '*.src' | \
xargs python3 ~/.openclaw/skills/greyscript-linter/scripts/lint-greyscript.py