Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Lint and format GDScript files using gdtoolkit (gdlint + gdformat). Use after writing or modifying .gd files, when asked to check code style, fix lint errors, format code, or set up linting configuration. Also use when gdlint/gdformat errors appear in output and need diagnosis. Does NOT require Godot — runs as a standalone Python tool.
.claude/skills/randallliuxin-gdtoolkit/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 51% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 78% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 32% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 91% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 146% | 0% |
> Currently disabled (v0.3.4+). gm-verify no longer invokes > gdlint / gdformat, and gm-build / gm-fixgap have removed this > skill from their Available Skills tables. Reason: repeated > gdtoolkit/linter/class_checks.py:144 NotImplementedError crashes on > common ECS-style GDScript class shapes, plus low signal-to-noise vs > the headless compile + reviewer pattern checks. Re-enabling tracked as > ROADMAP R-112. The reference content below is preserved for ad-hoc > use outside the pipeline and for the future re-enablement.
Wraps two CLI tools from the gdtoolkit Python package:
bashpip install "gdtoolkit==4.*" # Godot 4 projects pip install "gdtoolkit==3.*" # Godot 3 projects
Verify installation: gdlint --version && gdformat --version
If not installed, tell the user and offer to install. The version major must match the project's Godot major version (read from project.godot config/features).
bashgdlint path/to/file.gd # single file gdlint path/to/directory/ # all .gd files recursively
Each problem is one line on stderr:
path/to/file.gd:42: Error: Function name "MyFunc" is not valid (function-name)Format: {file}:{line}: Error: {message} ({rule-id})
Exit codes:
Success: no problems found)Failure: N problem(s) found)For each reported issue, either:
gdscript # gdlint:ignore = rule-id var _unusedButNeeded := 0 # this line + next line are suppressed
gdscript # gdlint: disable=function-name func ALLCAPS_required_by_engine(): pass # gdlint: enable=function-name
When suppressing, always add a brief comment explaining why.
bashgdformat path/to/file.gd # format in-place gdformat path/to/directory/ # format all .gd files gdformat --check path/ # check only, don't modify (exit 1 if changes needed) gdformat --diff path/ # show unified diff on stderr, don't modify
Exit codes:
gdformat runs three safety checks by default (disable with --fast):
If a safety check fails, report the error to the user — do NOT use --fast to bypass it. This likely indicates a gdtoolkit bug; the file should be formatted manually or the problematic section excluded.
| Rule ID | Default convention | Example | |---|---|---| | function-name | snake_case or _on_PascalCase_signal | move_player, _on_Button_pressed | | class-name | PascalCase | PlayerController | | sub-class-name | _PascalCase (leading underscore) | _InternalHelper | | signal-name | snake_case | health_changed | | class-variable-name | snake_case or _private | speed, _cache | | function-variable-name | snake_case | local_var | | function-argument-name | snake_case or _unused | target_pos, _ignored | | loop-variable-name | snake_case or _unused | item, _i | | constant-name | UPPER_SNAKE_CASE | MAX_SPEED | | enum-name | PascalCase | Direction | | enum-element-name | UPPER_SNAKE_CASE | NORTH, SOUTH_EAST |
| Rule ID | Default | What it checks | |---|---|---| | max-returns | 6 | Too many return statements per function | | max-public-methods | 20 | Too many public methods per class | | function-arguments-number | 10 | Too many function arguments | | max-file-lines | 1000 | File too long | | max-line-length | 100 | Line too long |
| Rule ID | What it checks | |---|---| | unnecessary-pass | pass in non-empty body | | duplicated-load | Same resource loaded twice | | expression-not-assigned | Standalone expression with no effect | | unused-argument | Argument never used (fix: prefix with _) | | comparison-with-itself | x == x | | private-method-call | Calling _private_method() from outside | | class-definitions-order | Members not in canonical order (see below) | | trailing-whitespace | Trailing spaces | | mixed-tabs-and-spaces | Mixed indentation | | no-elif-return | Unnecessary elif after return | | no-else-return | Unnecessary else after return |
gdlint expects this top-to-bottom order:
@toolclass_nameextends@export variables_prefixed)@onready public variables@onready private variablesCreate .gdlintrc (or gdlintrc) in the project root. YAML format. gdlint searches upward from CWD, uses the first file found.
Generate defaults: gdlint -d > .gdlintrc
Example with customizations:
yaml# Relax line length to match gdformat default max-line-length: 120 # Allow _on_NodeName_signal pattern (already default) function-name: '(_on_[A-Z][a-z0-9]*(_[a-z0-9]+)*|[a-z][a-z0-9]*(_[a-z0-9]+)*)' # Disable rules that conflict with project style disable: - unnecessary-pass # we use pass as explicit "intentionally empty" # Exclude generated/vendored code excluded_directories: !!set .git: null addons: null .godot: null
Create gdformatrc in the project root. YAML format.
Generate defaults: gdformat --dump-default-config > gdformatrc
yamlline_length: 120 # use_spaces: 4 # uncomment to use spaces instead of tabs excluded_directories: !!set .git: null addons: null .godot: null
gdtoolkit does NOT read from pyproject.toml. Only its own YAML config files work.
Set max-line-length in .gdlintrc and line_length in gdformatrc to the same value. If they differ, gdformat may merge lines that then exceed gdlint's limit — a known source of false positives.
# gdformat: off/on — there is no way to skip formatting for a code region.If gdformat mangles a specific construct, the only workaround is to restructure the code.
var x = 1 with nofurther use of x will NOT be flagged. Only function arguments trigger unused-argument.
excluded_directories only works when scanning directories — passing a file pathdirectly (gdlint addons/plugin/main.gd) bypasses exclusion rules.
--fast.Report to the user and format that section manually.
before formatting. Run git diff after formatting to verify changes are correct.
3.x parses Godot 3. Mismatched versions cause parse errors on valid code.
If this skill's instructions don't resolve your issue — unexpected output, unfamiliar rule IDs, config syntax errors, or parse failures — consult the upstream repo directly:
Use WebFetch to read the wiki pages or issue threads when you need details beyond what this skill covers.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-09 | fail→pass | 14,881 | 6,379 | -57% | 1 | 1 | 0% | 2,259 | 3,413 | +51% | 0 | 0 | — |
case-01 | fail→pass | 11,005 | 4,850 | -56% | 1 | 1 | 0% | 1,807 | 3,225 | +78% | 0 | 0 | — |
case-02 | fail→pass | 13,442 | 4,134 | -69% | 1 | 1 | 0% | 2,378 | 3,141 | +32% | 0 | 0 | — |
case-03 | pass→pass | 10,481 | 6,837 | -35% | 1 | 1 | 0% | 1,897 | 3,672 | +94% | 0 | 0 | — |
case-04 | pass→pass | 14,188 | 6,491 | -54% | 1 | 1 | 0% | 2,142 | 3,472 | +62% | 0 | 0 | — |
case-05 | fail→pass | 9,392 | 5,023 | -47% | 1 | 1 | 0% | 1,662 | 3,171 | +91% | 0 | 0 | — |
case-06 | fail→pass | 7,298 | 2,618 | -64% | 1 | 1 | 0% | 1,153 | 2,841 | +146% | 0 | 0 | — |
case-07 | pass→pass | 6,090 | 3,444 | -43% | 1 | 1 | 0% | 1,027 | 2,915 | +184% | 0 | 0 | — |
case-08 | pass→pass | 11,216 | 4,649 | -59% | 1 | 1 | 0% | 1,841 | 3,188 | +73% | 0 | 0 | — |
case-10 | fail→pass | 11,739 | 5,558 | -53% | 1 | 1 | 0% | 1,935 | 3,306 | +71% | 0 | 0 | — |
case-11 | fail→pass | 8,806 | 4,392 | -50% | 1 | 1 | 0% | 1,395 | 3,082 | +121% | 0 | 0 | — |
case-12 | fail→pass | 15,921 | 3,614 | -77% | 1 | 1 | 0% | 2,628 | 3,019 | +15% | 0 | 0 | — |
case-13 | pass→pass | 2,847 | 2,983 | +5% | 1 | 1 | 0% | 447 | 2,843 | +536% | 0 | 0 | — |
case-14 | fail→pass | 11,255 | 2,713 | -76% | 1 | 1 | 0% | 1,740 | 2,840 | +63% | 0 | 0 | — |
case-15 | pass→pass | 9,887 | 4,710 | -52% | 1 | 1 | 0% | 1,663 | 3,213 | +93% | 0 | 0 | — |
case-16 | pass→pass | 5,878 | 3,166 | -46% | 1 | 1 | 0% | 946 | 2,820 | +198% | 0 | 0 | — |
case-17 | pass→pass | 11,630 | 2,932 | -75% | 1 | 1 | 0% | 1,577 | 2,852 | +81% | 0 | 0 | — |
case-18 | pass→pass | 8,477 | 2,430 | -71% | 1 | 1 | 0% | 1,415 | 2,740 | +94% | 0 | 0 | — |
case-19 | pass→pass | 7,425 | 2,085 | -72% | 1 | 1 | 0% | 1,085 | 2,685 | +147% | 0 | 0 | — |
case-20 | pass→pass | 11,352 | 7,399 | -35% | 1 | 1 | 0% | 2,056 | 3,727 | +81% | 0 | 0 | — |
case-21 | pass→pass | 10,333 | 6,815 | -34% | 1 | 1 | 0% | 1,718 | 3,590 | +109% | 0 | 0 | — |
case-22 | fail→fail | 10,632 | 13,414 | +26% | 1 | 1 | 0% | 2,133 | 4,898 | +130% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +41 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.