Skip to content

The eight gates

cdowin edited this page Oct 4, 2026 · 1 revision

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.

Clone this wiki locally