gate-sdk
A self-testing lint framework for the surfaces conventional linters ignore: markdown specs, glossaries, task queues, config projections, diagrams — any text whose drift is mechanically decidable. Built for repos where coding agents do the writing: a stateless agent session cannot be trusted to remember conventions, so the conventions are enforced by machine-run gates that block the commit instead.
A gate is a small program checking one invariant across one or more governed surfaces. The kit ships the machinery that keeps a gate family honest:
lib/gate.sh— the one sourced helper: thefail_closedwrapper (a crashed parser must never read as “clean”), the fixture-tree prune adapters, and the registry/resolution helpers.bin/run-gates.sh— the aggregate battery: every gate in yourgates.list, one shot, per-gate timings. Two selectors narrow it without losing the knob files or the output contract:--only <name>...runs the gates you name (in registry order; an unregistered name is a refusal), and--for <path>...runs the gates coupling to the paths you name, exactly as the pre-commit hook would. On a native-Windows host with no bash onPATH, its PowerShell twinbin/run-gates.ps1takes the same arguments.- the
--run-gate-testsarm — the golden-fixture runner: every gate proves it accepts agood/case and rejects abad/case with the right error text. - the
--run-consumer-smokearm — the end-to-end check no fixture makes: builds a fresh scratch consumer, runs each vendored kit’ssmoke/installer, and asserts the battery is green under zero config (then red on each kit’s crafted violation). Each kit ships asmoke/directory to join the party. - the
--install-hooksarm — places thepre-commitandcommit-msghooks in a clone, each the gate binary under the hook’s name, which git starts with no shell; what a hook runs is read at commit from per-gate# graph:coupling manifests (tier=precommitvstier=commit-msg), so adding a gate to a hook is manifest-only and hook membership cannot drift. A manifest’s trigger reach is read with--for, never off the field (SPEC.md §Reading acouples=field’s reach). bin/build-native.sh— the one spelling of the crate build for the binary substrate: resolves the crate fromGATE_SDK_NATIVE_CRATE, passes trailing arguments to cargo (so a per-target build reuses it), and returns cargo’s own exit code. Every reader of that command cites this script rather than copying it.checks/— the meta-gates that hold the family to its own standard: ShellCheck self-lint, the output contract, the fail-closed contract, fixture coverage, SPEC↔code assertion coupling, exemption-list hygiene, and manifest / graph-artifact freshness (check-graph).templates/check-skeleton.sh— the copy-paste skeleton a new gate starts from;templates/gates-workflow.yml— the CI workflow a consumer copies to.github/workflows/gates.yml;templates/adopt.md— the walk an agent follows afterinit: knobs fitted to your layout, first reds triaged.
Enforcement runs in three concentric tiers, each an outer backstop for the one inside it: the pre-commit hook, the local and bypassable inner tier; run-gates.sh’s pre-push full battery, whole-tree before the work leaves the machine; and the CI workflow, the server-side backstop that catches a --no-verify or a clone that never opted in. Only CI is a guarantee — see SPEC.md §Enforcement tiers for the full definition, including the deferred hosted-attestation rung beyond these three (so CI cannot be edited away in the same change) that is out of scope here.
The design contracts, the manifest grammar, and each component’s full contract live in SPEC.md.
This file ships in the installer payload; what that payload withholds, and where this page’s SPEC.md links then resolve, is SPEC.md §Consumer payload.
Quick start
Vendor the kit into your repo at gate-sdk/. Never edit a vendored kit file: configuration lives outside it, so an upgrade replaces the directory losslessly. Then:
mkdir -p scripts # your gates dir (GATE_SDK_GATES_DIR to relocate)
cat > scripts/gates.list <<'EOF'
# kit meta-gates (resolve from gate-sdk/checks/)
check-shellcheck
check-gate-output
check-gate-fail-closed
check-gate-fixture-coverage
check-gate-exemption-tasks
check-gate-assertions
check-graph
EOF
mkdir -p .workflow docs
Then run these arms with the gate binary GATE_SDK_NATIVE_BIN names, where init places it. In PowerShell, spell each line ./scripts/checkwright-gates <arm> at the repository root, with .exe on native Windows:
gates="${GATE_SDK_NATIVE_BIN:-./scripts/checkwright-gates}"
"$gates" --emit graph > scripts/CHECK-GRAPH.html # the coupling graph
"$gates" --emit enforcement-map > docs/enforcement.md # the enforcement map (regenerate on any class-registry change)
"$gates" --emit port-blockers --tree # the port report over the tracked shell tree
"$gates" --install-hooks # place the hooks in this clone
"$gates" --run # the full battery
"$gates" --only check-graph # one gate's verdict
"$gates" --for scripts/gates.list # the gates coupling to a path
"$gates" --run-gate-tests gate-sdk/gate-tests gate-sdk/checks # the kit's own tests
Write your first gate by copying gate-sdk/templates/check-skeleton.sh to scripts/check-<area>.sh, editing it, adding check-<area> to scripts/gates.list, and shipping a scripts/gate-tests/check-<area>/{good,bad}/ fixture pair. The meta-gates will hold you to the rest.
Gates
The Quick start registers the subset a new consumer begins with; gate-sdk ships the full meta-gate roster below, each contract in SPEC.md:
check-shellcheck
check-gate-output
check-gate-fail-closed
check-gate-fixture-coverage
check-gate-assertions
check-gate-substrate-parity
check-install-disposition
check-test-hermetic
check-path-dialect
check-pipe-membership
check-front-end-fail-open
check-assertion-strength
check-gate-exemption-tasks
check-graph
check-reads-couples
check-enforcement-fresh
check-kit-enum
check-kit-registration
check-readme-roster
check-smoke-entry-guard
check-core-files
check-identity
check-exec-bit
check-root-tiering
check-workflow-tiering
check-action-pinning
check-action-run-shell
check-action-run-path
check-action-gh-repo
check-action-permissions
check-action-job-ref
check-commit-msg
check-commit-subject
check-tree-terms
check-portability-floor
check-projection-roster
check-template-copy-parity
check-template-registry-parity
Requirements
The toolchain contract is the table in docs/requirements.md §Installing and running the shipped gates, with the contributor’s rows in CONTRIBUTING.md: the roster and each pinned floor. The constructs that force each floor are installer/SPEC.md §Requirements’. check-install-toolchain holds both tables to native/src/toolfloor.rs, the roster’s owner, so the requirement has one statement and no copy.
License
Licensed under Apache-2.0, with the text in LICENSE beside this file in an installed copy and at the repository root in the source tree.