Gdscript quality
AI agent skills
npx -y skills add oubakiou/skills --skill gdscript-qualityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Write production-quality, statically typed GDScript for Godot 4.x. Use this skill whenever you create, edit, refactor, or review .gd files or any Godot project code — even if the user does not mention types, warnings, or code quality ("add a feature to my Godot game" or "fix this script" counts). Covers type annotations, typed Arrays and Dictionaries, eliminating UNTYPED_*/UNSAFE_* warnings, GDScript naming and script organization, node access and signal patterns, and headless verification with the bundled check script.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
8.2 KB, as published. Nobody here has run it
GDScript Quality
Produce GDScript that compiles clean under Godot's strict warnings and reads like code written by a careful Godot engineer. Static typing is not cosmetic: the analyzer catches real bugs before the game runs, the editor gains full autocompletion, and typed opcodes execute faster. Untyped code silently forfeits all three, and the cost surfaces later as runtime errors that types would have caught at parse time.
Workflow
-
Enable strict warnings in
project.godotfirst (see below), so anyone opening the project in the editor sees the same findings the check script enforces. -
Write code following the core rules in this file. Consult
references/for anything beyond the basics (see the map below). -
Verify headlessly after implementing:
bash <skill_dir>/scripts/check.sh <project_dir>Fix every reported finding and re-run until it prints
RESULT: CLEAN. If nogodotbinary is available (exit code 3), say explicitly in your final report that headless verification was skipped — do not present unverified code as verified.
Warning configuration
Ensure project.godot has a [debug] section containing:
[debug]
gdscript/warnings/untyped_declaration=1
gdscript/warnings/unsafe_property_access=1
gdscript/warnings/unsafe_method_access=1
gdscript/warnings/unsafe_cast=1
gdscript/warnings/unsafe_call_argument=1
Values: 0 ignore, 1 warn, 2 error. Know one engine quirk: at 1 the
warnings appear only in the editor UI — headless runs print nothing.
Warnings surface on the command line only at 2, as
SCRIPT ERROR: ... (Warning treated as error.). The check script handles
this by escalating to 2 during the check, so 1 is a fine everyday
setting; use 2 in the project itself when you want the game to refuse to
run until warnings are fixed.
Core rules
Type every declaration. Variables, constants, parameters, return types —
including -> void when a function returns nothing, and including private
helpers and loop-local temporaries. Partial typing is where bugs hide: one
untyped field turns every access on it into an unchecked Variant operation
that the analyzer can no longer help you with.
Use := only when the right-hand side makes the type obvious (a literal,
a constructor call, or a cast: var speed := 12.5,
var rng := RandomNumberGenerator.new()). Fields, function signatures, and
anything part of a class's surface get explicit annotations — readers see the
type without hunting for the initializer.
Type your collections. Array[Item], Dictionary[Vector2i, Item]
(typed dictionaries need Godot 4.4+ — check config/features in
project.godot first; on 4.0–4.3 keep the Dictionary untyped and type the
values at each access with typed locals or casts instead). Nested generics
(Array[Array[int]]) are a syntax error — declare the outer collection as
Array[Array] and give inner rows a typed local
(var row: Array[int] = grid[y]), or wrap the row in a small class.
Never model data with untyped Dictionaries. A dict-as-struct
({"hp": 10, "name": "orc"}) has no spelling check on keys, no type on
values, and every access is unsafe. Define a class_name script or an inner
class ... extends RefCounted with typed fields instead. Reserve Dictionary
for genuinely dynamic key→value lookups, and type it when you do.
Name your magic values. Tuning numbers, grid sizes, thresholds, string
keys: const MOVE_SPEED: float = 50.0. Use enum for closed sets of
alternatives instead of ints or strings. (Enum types are ints underneath —
the type does not guarantee a value is a member, so validate external input.)
Type loop variables when the iterable is untyped:
for i: int in range(n), for line: String in lines. A typed
Array[T]/Dictionary[K, V] already types its loop variable for you.
Narrow Variants safely. When you receive a base type or Variant
(signal callbacks, get(), JSON), do not access members on it directly —
that is exactly what UNSAFE_PROPERTY_ACCESS/UNSAFE_METHOD_ACCESS flag.
Narrow first:
if body is Player:
var player: Player = body
player.take_damage(amount)
as also works (var player := body as Player) but silently yields null
on mismatch, so it demands an explicit null check — prefer is + a typed
local unless the silent-null behavior is what you want. Remediation recipes
for each UNSAFE_* warning are in references/typing.md.
Give node references explicit types:
@onready var timer: Timer = $Timer. The explicit annotation fails loudly at
scene load if the node type doesn't match; $Timer as Timer would silently
become null and blow up later, far from the cause.
Use the typed math builtins. absf/absi, clampf/clampi,
floorf/floori, lerpf, roundf/roundi, etc., instead of the Variant
abs()/clamp()/... — the untyped versions cost you both safety and speed.
Full table in references/typing.md.
Type signal parameters (signal died(cause: StringName)) and the
callbacks that receive them — signals are API surface too.
Suppress warnings only as a last resort. @warning_ignore("...") on a
single line, with a comment stating the constraint that makes the warning
unavoidable. If you cannot state one, fix the code instead. Never turn a
warning off project-wide to make output clean.
Reference map
Read these on demand — not all up front:
references/typing.md— the full typing reference: what can be a type hint, typed collection semantics and their limits, casting andisnarrowing, per-warning remediation recipes (UNSAFE_PROPERTY_ACCESS, UNSAFE_METHOD_ACCESS, UNSAFE_CAST), typed builtin table, covariance/contravariance, warning-system configuration. Read when fighting a specific warning or typing anything non-obvious.references/style.md— naming conventions, canonical declaration order inside a script, formatting,##doc comments. Read before writing any new script file.references/architecture.md— separating simulation logic from Node presentation, node access patterns, signal design ("signals up, calls down"), deterministic RNG injection, and a minimal headless test-script pattern. Read when creating new scenes/systems or restructuring.
Verification script
scripts/check.sh <project_dir> temporarily escalates this skill's strict
warning set to errors in project.godot (restored afterward, also on
interrupt), runs --import to build the class cache and catch
scene/resource errors, then validates every first-party .gd file with
--check-only — including scripts no scene references. Warnings surface as
SCRIPT ERROR: ... (Warning treated as error.) lines with file and line.
- Exit 0: clean. Exit 1: findings reported — fix and re-run.
- Exit 2: usage error (not a Godot project dir). Exit 3: no
godotbinary (GODOT_BINenv var overrides the binary name). addons/is skipped (third-party code is not yours to fix).- It performs static analysis only; it does not run the game or its tests, so run the project's own test entry point separately if one exists.
When findings fall outside your scope. check.sh checks the whole
project, so it can report NOT CLEAN from files you did not touch and are
not allowed to touch (a fixed test oracle, pre-existing code the task
scoped you out of). In that case do not edit protected files just to
silence the gate, and do not claim the check passed. Report both facts:
the files you changed contribute zero findings, and the residual findings
are pre-existing in out-of-scope files (name them). Verify the "pre-existing"
claim before making it — confirm the same findings appear against the
original code, not just assume it.