Skip to content

AxeForging/gauntlet

Repository files navigation

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.

Install without running Gauntlet in GitHub Actions:

- uses: AxeForging/gauntlet/setup@v0.1.1
  with:
    version: v0.1.1

Pin both references for reproducible CI; use version: latest only for an intentional floating install.

To intentionally track the newest published release, set version: latest.

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

Gauntlet dogfoods its own orchestration through .gauntlet.yml: Structlint runs as a whole-repository structural invariant, while Dupehound annotations are scoped to the PR diff. Install both released gates before reproducing CI locally:

go install github.com/AxeForging/structlint@v0.6.0
go install github.com/AxeForging/dupehound@v0.1.0
gauntlet check --staged --format agent

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

License

MIT — see LICENSE.

About

Unified quality-gate runner with diff-scoped, verdict-first output for coding-agent loops

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors