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: the fail_closed wrapper (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 your gates.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 on PATH, its PowerShell twin bin/run-gates.ps1 takes the same arguments.
  • the --run-gate-tests arm — the golden-fixture runner: every gate proves it accepts a good/ case and rejects a bad/ case with the right error text.
  • the --run-consumer-smoke arm — the end-to-end check no fixture makes: builds a fresh scratch consumer, runs each vendored kit’s smoke/ installer, and asserts the battery is green under zero config (then red on each kit’s crafted violation). Each kit ships a smoke/ directory to join the party.
  • the --install-hooks arm — places the pre-commit and commit-msg hooks 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=precommit vs tier=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 a couples= field’s reach).
  • bin/build-native.sh — the one spelling of the crate build for the binary substrate: resolves the crate from GATE_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 after init: 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.