gauntlet

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2026 License: MIT

README

gauntlet

One binary, every repo: is my change green, and if not, exactly what do I fix?

Unified quality-gate runner with diff-scoped, verdict-first output. Built for coding-agent loops, equally usable by humans, lefthook, and CI.

$ gauntlet check --staged --format agent
FAIL — 3 findings on your changes (14 pre-existing suppressed) [gofumpt:ok, govet:ok, golangci:fail, gotest:fail, gobuild:ok]

golangci  ERROR internal/engine/plan.go:42            unused-parameter     parameter 'ctx' is unused
golangci  ERROR internal/engine/plan.go:87            errcheck             return value of f.Close() is not checked
gotest    ERROR internal/engine/plan_test.go:33       test-failure         TestPlan_DetectsDrift failed: expected 1, got 2

Install

# Latest checksum-verified release:
curl -fsSL https://raw.githubusercontent.com/AxeForging/gauntlet/main/install.sh | sh

# Pin a version or install somewhere else:
curl -fsSL https://raw.githubusercontent.com/AxeForging/gauntlet/main/install.sh | \
  GAUNTLET_VERSION=v0.1.0 GAUNTLET_INSTALL_DIR="$HOME/.local/bin" sh

# From source:
go install github.com/AxeForging/gauntlet/cmd/gauntlet@main

Requires Go 1.25+ for source installation. Release binaries have no Go runtime dependency. Optional gate tools (skipped visibly if absent): gofumpt, golangci-lint, prettier, eslint, tsc, vitest.

CI usage

Run gauntlet on every PR via the composite action:

# .github/workflows/pr.yml
jobs:
  gauntlet:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0        # required so gauntlet can diff against base
      - uses: AxeForging/gauntlet@main
        # optionally: with: { version: v1.0.0, gates: golangci,gotest }

The action installs the pinned gauntlet binary and runs gauntlet check --format github --since $GITHUB_BASE_REF. Findings appear as inline annotations on the PR diff. Same binary as local — local green == CI green.

CI auto-detection: when GITHUB_ACTIONS=true is set (which the action's runner does automatically), gauntlet defaults --format to github and picks --since from GITHUB_BASE_REF unless the user passes an explicit override.

Usage

gauntlet check                              # whole-tree scan
gauntlet check --staged                     # pre-commit scope (diff staged)
gauntlet check --since main                 # branch-vs-base-ref scope
gauntlet check --gate golangci --gate gotest
gauntlet check --format json                # machine-readable, schema: gauntlet/v1
Flags
Flag Effect
--staged Scope to changes staged for commit. Mutually exclusive with --since.
--since <ref> Scope to changes vs <ref>...HEAD (three-dot: merge-base of ref and HEAD).
--format human|agent|json|github Default human. agent = verdict-first, ranked, capped. json = versioned schema. github = GitHub Actions workflow annotations (inline on the PR diff).
--gate <name> Repeatable. Run only the listed gates.
--max-findings <N> Cap for --format agent ranked list (default 40). Overflow is announced with a count; never truncates a finding mid-line.
--explain <id> Print full detail for one finding id (<gate>:<file>:<line>:<rule>, printed in every format). Ideal for drill-in when the compact agent list omits context.
--log-level trace|debug|info|warn|error, default info.
--log-json Emit structured JSON logs on stderr.
Exit codes
Code Meaning
0 Green — no new findings.
1 New findings — the verdict is FAIL.
2 Gauntlet-level error — no supported stack, bad config, no git repo, unknown flag.

A missing optional tool (e.g. golangci-lint not installed) prints a visible SKIP and does not force a non-zero exit on its own. It never silently passes.

.gauntlet.yml

Zero-config by default. To override:

disable:
  - golangci        # skip a gate even when the stack lists it
base_ref: main      # default ref used by --since when not passed explicitly
gates:
  golangci:
    args: ["--timeout=10m"]

Unknown keys are hard errors — a typo won't silently disable your gate.

Output schema

--format json stamps every response with "schema": "gauntlet/v1". The full schema doc lives at docs/schema/gauntlet-v1.schema.json; any breaking change bumps to v2 and consumers reading v1 keep working. Additive fields don't bump the version. Drift is caught by an automated golden test in the repo.

Fix hints

The agent + human formats append a one-line fix hint under each finding when the <gate>:<rule> combination has a hint entry — e.g. errcheck, unused-parameter, TS2322, TS7006, no-unused-vars. Unknown rules get no hint (misleading hints are worse than none). Add hints in internal/report/hints.go; see the file's header for the contribution rule.

Design

  • Diff-scoped by default — a lint finding on a line you didn't touch is pre-existing, counted but not shown. Test/build failures are never suppressed (a broken test can live in an untouched file).
  • Verdict first — the first output line is the whole answer:
    FAIL — N findings on your changes (M pre-existing suppressed) [gate:status ...]
    
  • No silent passes — missing tools show as SKIP. Never || true.
  • Machine formats only--out-format json, -json, or the stable Go tool file:line:col: msg line format. A stack is supported only when its tools have a machine reporter.
  • Policy in one file.gauntlet.yml, not shell snippets scattered across hooks.

Supported stacks

Stack Marker Gates
Go go.mod gofumpt → govet → golangci-lint → gotest → gobuild
TypeScript / JavaScript package.json prettier → eslint → oxlint† → tsc → vitest → tsbuild

† oxlint's -f json shape isn't ESLint-compatible; gate always SKIPs in the current release. See plan 002 for the follow-up.

Every JS gate checks node_modules/.bin/<tool> first and SKIPs visibly when the tool isn't installed in the project — no cryptic pm errors, no silent passes.

Mixed-stack repos (Go binary + committed web UI at the same root — like loadout) run both pipelines in registry order and emit a single merged verdict.

Any other language / tool: .gauntlet.yml supports custom gates with four built-in parser kinds (json-findings, file-line-col, github-annotations, exit-code-only). Run one argv-safe command or connect multiple commands through a shell-free pipeline. Plug in structlint, dupehound, ruff, tflint, Pipekit transforms, or proprietary tools without changing Gauntlet. See custom gate documentation.

Development

make ci                 # golangci-lint + go test -race
make test-integration   # bin-driven scenarios in temp repos
make build              # bin/gauntlet with real ldflag build info

Spec: docs/specs/001-gauntlet-core.md. Plan: docs/specs/001-gauntlet-core.plan.md. Roadmap: ROADMAP.md.

License

MIT — see LICENSE.

Directories

Path Synopsis
cmd
gauntlet command
gauntlet — unified quality-gate runner with diff-scoped, verdict-first output.
gauntlet — unified quality-gate runner with diff-scoped, verdict-first output.
internal
app
Package app is the composition root: wires the CLI tree together and exposes the Run entrypoint used by cmd/gauntlet/main.go.
Package app is the composition root: wires the CLI tree together and exposes the Run entrypoint used by cmd/gauntlet/main.go.
build
Package build carries version information injected at link time via -ldflags.
Package build carries version information injected at link time via -ldflags.
cli
Package cli builds the top-level urfave/cli/v3 command tree.
Package cli builds the top-level urfave/cli/v3 command tree.
config
Package config loads .gauntlet.yml.
Package config loads .gauntlet.yml.
doctor
Package doctor runs a set of preflight checks: git state, stack detection, config validity, tool availability.
Package doctor runs a set of preflight checks: git state, stack detection, config validity, tool availability.
domain
Package domain holds the shared types every layer speaks: findings, gate results, verdicts.
Package domain holds the shared types every layer speaks: findings, gate results, verdicts.
engine
Package engine orchestrates gate execution and applies diff scoping.
Package engine orchestrates gate execution and applies diff scoping.
gates
Package gates defines the Gate contract every tool wrapper implements.
Package gates defines the Gate contract every tool wrapper implements.
gates/customgate
Package customgate implements a generic Gate that runs an arbitrary command and parses its output via a built-in parser kind.
Package customgate implements a generic Gate that runs an arbitrary command and parses its output via a built-in parser kind.
gates/eslint
Package eslint wraps `<pm exec> eslint --format json .`.
Package eslint wraps `<pm exec> eslint --format json .`.
gates/gobuild
Package gobuild wraps `go build ./...`.
Package gobuild wraps `go build ./...`.
gates/gofumpt
Package gofumpt wraps `gofumpt -l` as a gauntlet gate.
Package gofumpt wraps `gofumpt -l` as a gauntlet gate.
gates/golangci
Package golangci wraps `golangci-lint run --out-format json`.
Package golangci wraps `golangci-lint run --out-format json`.
gates/gotest
Package gotest wraps `go test -json -race ./...`.
Package gotest wraps `go test -json -race ./...`.
gates/govet
Package govet wraps `go vet ./...`.
Package govet wraps `go vet ./...`.
gates/jslint
Package jslint holds the shared ESLint-style JSON parser used by both the eslint and oxlint gates — they emit the same array-of-file-results shape.
Package jslint holds the shared ESLint-style JSON parser used by both the eslint and oxlint gates — they emit the same array-of-file-results shape.
gates/oxlint
Package oxlint wraps oxlint as a gauntlet gate.
Package oxlint wraps oxlint as a gauntlet gate.
gates/prettier
Package prettier wraps `<pm> exec prettier --check --list-different .`.
Package prettier wraps `<pm> exec prettier --check --list-different .`.
gates/tsbuild
Package tsbuild runs the repo's `build` script (`<pm> run build`).
Package tsbuild runs the repo's `build` script (`<pm> run build`).
gates/tsc
Package tsc wraps `<pm exec> tsc --noEmit --pretty false`.
Package tsc wraps `<pm exec> tsc --noEmit --pretty false`.
gates/vitest
Package vitest wraps `<pm exec> vitest run --reporter=json`.
Package vitest wraps `<pm exec> vitest run --reporter=json`.
gitdiff
Package gitdiff shells out to system git to compute which lines in which files a change touches.
Package gitdiff shells out to system git to compute which lines in which files a change touches.
logging
Package logging configures the process-wide zerolog logger.
Package logging configures the process-wide zerolog logger.
nodepm
Package nodepm detects which Node package manager governs a repo (pnpm, yarn, npm) from its lockfile.
Package nodepm detects which Node package manager governs a repo (pnpm, yarn, npm) from its lockfile.
report
Package report renders a Verdict into one of three formats: human (TTY table), agent (verdict-first, compact, machine-scannable), or json (versioned stable schema).
Package report renders a Verdict into one of three formats: human (TTY table), agent (verdict-first, compact, machine-scannable), or json (versioned stable schema).
stack
Package stack maps a repo root to the ordered gate list that should run in it.
Package stack maps a repo root to the ordered gate list that should run in it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL