Repository navigation
The eight gates
Every gate prints a census of what it scanned, then a verdict — on the FAIL line as much as the
PASS line. Read the census first: a gate that scanned fewer files than you expected is telling you
your config is wrong, not that your tree is clean. A zero-file census FAILS rather than passing, a
file that could not be decoded is reported and dropped from the count, and nothing is a finding
unless the whole picture resolved — anything unresolvable is censused UNVERIFIED, never failed.
| gate | scans | fails on | config |
|---|---|---|---|
uid |
tracked .tscn/.tres Script refs against .gd.uid sidecars; new .gd (untracked or staged); tracked sidecars |
a stale ref uid, a script with no sidecar, an orphan sidecar, a non-canonical uid spelling — --fix repairs what has a should-be value |
[uid] exclude_prefixes |
tres |
tracked .tscn/.tres ext_resource lines |
a path-only ref (no uid=), which Godot 4.4+ rewrites silently on the next editor pass |
[tres] exclude_prefixes, baseline
|
props |
every property assignment in tracked scenes, against the script's @exports and Godot's ClassDB |
an assignment to a property that does not exist (a renamed export, a mistyped built-in) |
[props] exclude_prefixes, extra_properties, baseline
|
defaults |
tracked .tres assignments against the script's declared @export defaults |
an assignment equal to its default — the churn Godot's writer omits and a hand-authored file spells out |
[defaults] exclude_prefixes, baseline
|
rng |
.gd under the configured roots |
a bare randi()/randf()/randi_range()/randf_range() or any randomize() — a draw a seeded run does not own |
[rng] roots, allowlist, baseline
|
tres-comment |
tracked .tscn/.tres
|
a line opening with ; — a comment Godot's serializer drops on the next save |
[tres_comment] exclude_prefixes, baseline
|
unit-disk |
.gd under the unit-test roots |
a user:// literal, a forbidden call, or a save/settings call given fewer arguments than its real-root default needs |
[unit_disk] roots, forbidden_literals, forbidden_calls, min_args, baseline
|
canonical (opt-in) |
tracked .tres values and property order against what Godot's saver writes, where a parse can prove it |
a float the saver spells shorter (0.30 -> 0.3), a bare list on an Array[T] export, a scripted section out of declaration order — the churn an editor save of a hand-authored resource makes |
[canonical] exclude_prefixes; not in the stock check all — name it in [roster] checks
|
test-shape |
the integration tier | a new scenario over the line cap, a ledgered one that grew, and — with header = true — a scenario with no ## covers:/## Boots because: header (a ledgered one is held until it carries both), asked of the roster make integration-list boots |
[test_shape] scenario_root, cap, infra, ledger, header, header_ledger, runner
|
Exit codes are contract: 0 pass · 1 findings · 2 usage or config error. A godot-devkit.toml
mistake is always 2, so CI can never read a typo as drift, and a key this package does not honour
is named at exit 2, never ignored.
Adopting the gates on an existing tree, in order, because some start red by design: check uid
(commit sidecars with their scripts; --fix clears stale refs, spellings and orphans) →
migrate to uid-in-refs, then check tres → check props (findings are real renamed-export bugs) →
scene canonicalize --elide-defaults, then check defaults → scene canonicalize --respell --order,
then check canonical (add it to [roster] checks) → wire check all. Steps two and four
are also the cure for .tscn/.tres churn — files you did not edit turning up in every commit.
Adopting a gate frozen. Six gates — tres, props, defaults, rng, tres-comment,
unit-disk — take a baseline in their own section: one entry per file, at the number of findings
it carries today. Those findings are held, and every run prints BASELINED N finding(s) in M file(s) frozen by [<section>] baseline so the debt stays visible. A file whose findings grow past
its entry fails with every one of them listed; a file that now carries fewer fails until its entry
is lowered (SHRUNK) or dropped (STALE) — the baseline only shrinks. It is per file on purpose:
a single total would let a fix in one file pay for new drift in another. test-shape has the same
ratchet as its ledger, measured in lines.
Migrating to uid-in-refs: for a target whose header has no uid at all, mint one with Godot's own
ResourceUID.create_id() in a headless pass (never hand-author a uid string — an invalid uid
poisons the cache); inject each target's uid into the referencing ext_resource lines; prove it
cold — delete .godot/, run a headless --import, confirm zero invalid UID warnings.
Config for every gate: Configuration reference. When a gate fails: Troubleshooting.
Getting started
Reference
Guides
Upgrading
Maintaining