A CLI toolkit for testing, linting, AI-powered code review, and CI automation.
# From source
go install github.com/flanksource/gavel/cmd/gavel@latest
# Or build locally
task build # also: make build
task install # installs to $GOPATH/binPre-built binaries for Linux, macOS, and Windows are available on the Releases page.
For embedded and externally managed database observability, top-statement queries, and safe session-storage maintenance, see PostgreSQL performance diagnostics.
Run gavel test --lint in CI, upload JSON + HTML artifacts, and post a sticky PR comment with the markdown summary.
- uses: flanksource/gavel@main
with:
args: test --lintjobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # required when comment=true so the action can post/update the sticky PR comment
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: flanksource/gavel@main
with:
args: test --lint
timeout-minutes: "20"
version: latest
json-file: gavel-results.json
html-file: gavel-results.html
summary-file: gavel-summary.md
artifact-name: gavel-results
comment: "true"
comment-header: gavel
fail-on-error: "true"If you disable PR commenting (comment: "false"), contents: read is sufficient. Keep pull-requests: write when the action should post or update the sticky PR comment.
| Input | Default | Description |
|---|---|---|
args |
test --lint |
Arguments passed to gavel |
working-directory |
. |
Working directory for execution |
timeout-minutes |
20 |
Hard wall-clock limit; timeout diagnostics are printed before the process tree is terminated |
version |
latest |
Release tag, latest, or source to use a pre-installed binary |
json-file |
gavel-results.json |
Path for the JSON artifact |
html-file |
gavel-results.html |
Path for the HTML artifact |
summary-file |
gavel-summary.md |
Path for the compact markdown summary |
artifact-name |
gavel-results |
Name of the uploaded artifact bundle |
comment |
true |
Post/update a sticky PR comment with the summary |
comment-header |
gavel |
Unique marker for the sticky comment (allows multiple comments per PR) |
github-token |
${{ github.token }} |
Token used to post PR comments |
fail-on-error |
true |
Fail the step when gavel exits non-zero (artifacts are still uploaded) |
| Output | Description |
|---|---|
exit-code |
Exit code from gavel |
json-path |
Absolute path to the JSON artifact |
html-path |
Absolute path to the HTML artifact |
summary-path |
Absolute path to the markdown summary |
log-path |
Absolute path to the stderr log (gavel.log) — always present, even on crash |
The action is crash-resilient: if gavel exits before writing results, placeholder stubs with the exit code and log tail are written so the artifact upload and PR comment still succeed.
Discover and run Go and Ginkgo tests with structured output, optional linting, benchmarks, fixture files, and a live browser UI.
gavel test ./pkg/...
gavel test --lint # run linters in parallel with tests
gavel test --ui # launch browser dashboard
gavel test --changed # only packages affected by local changes
gavel test --cache # skip packages unchanged since last pass
gavel test --bench . # run benchmarks alongside tests
gavel test --fixtures # discover and run *.fixture.md files
gavel test --format "json=out.json,html=report.html" # write multiple output formats
gavel test --dry-run # show what would run without executing
gavel test history # show local test duration/pass history
gavel test ./pkg -- -run 'TestFoo' # focus Go test and Ginkgo by name
gavel test ./pkg -- --focus 'TestFoo' # equivalent native focus spelling
gavel test ginkgo ./pkg -- --label-filter smoke # raw args for one frameworkThe first -run or --focus immediately after -- is normalized through
each runner's focus mapping. Any other arguments after -- require exactly
one selected framework, using a framework subcommand or a single
--framework value.
| Flag | Description |
|---|---|
[paths...] |
Package paths to test (e.g. ./pkg/...). If empty, all packages are discovered. |
--lint |
Run linters in parallel with tests |
--ui |
Launch browser with real-time test progress dashboard |
--addr |
Interface to bind the UI server (default: 0.0.0.0, all interfaces; set localhost to restrict to this machine) |
--cache |
Skip packages whose content fingerprint matches the last passing run |
--changed |
Only run packages affected by staged/unstaged/untracked changes vs origin/main |
--since |
Only run packages affected by the diff since <ref> (merge-base) |
--bench |
Run Go benchmarks matching this regex (. runs all) |
--fixtures |
Discover and run fixture files (also enabled via .gavel.yaml) |
--fixture-files |
Globs for fixture discovery (default: **/*.fixture.md) |
-p / --nodes |
Number of parallel Ginkgo nodes (0 = default, -1 = auto) |
-r / --recursive |
Recursive test discovery (default: true) |
--show-passed |
Show passed tests in output |
--show-stdout |
When to show stdout: Never, OnFailure (default), Always |
--show-stderr |
When to show stderr: Never, OnFailure (default), Always |
--skip-hooks |
Skip .gavel.yaml pre/post hooks (default: skip locally, run in CI) |
--dry-run |
Print test commands without executing |
--auto-stop |
With --ui, fork a detached UI server that exits after this duration |
--idle-timeout |
With --ui --auto-stop, exit the detached UI after no HTTP requests |
--extra-args |
Additional arguments broadcast to every selected test runner |
--work-dir |
Working directory to run tests in |
Show a package/file/suite outline of executable tests from completed .gavel/run-*.json snapshots.
gavel test history
gavel test history ./pkg/foo
gavel test history pkg/foo/foo_test.go --format jsonEach leaf row shows min/avg/max duration, execution count, pass rate, last passed, last failed, and the date the test first appeared in local history. Optional paths filter by package or file relative to --cwd.
Run linters on the project. Auto-detects which linters are installed and applicable.
Supported linters: golangci-lint, ruff, eslint, oxlint, react-doctor, pyright, tsc, markdownlint, vale, jscpd, betterleaks.
gavel lint
gavel lint --fix # auto-fix violations
gavel lint --triage # interactive mode to select violations to ignore
gavel lint --changed # only new issues vs origin/main
gavel lint --ui # view violations in browser
gavel lint --dry-run # show linter commands without executing
gavel lint secrets # run betterleaks only (alias)
gavel lint react-doctor # run React Doctor only
gavel lint jscpd eslint # run specific linters by name| Flag | Description |
|---|---|
[linters/files...] |
Positional args: linter names run only those linters; file paths lint only those files |
--linters |
Comma-separated linter names or * for all (default: *) |
--fix |
Enable auto-fixing |
--triage |
Interactive mode to select violation types to ignore (saves to .gavel.yaml) |
--changed |
Only report new issues vs origin/main (or $GAVEL_CHANGED_BASE) |
--since |
Only report new issues since <ref> (merge-base with HEAD) |
--ignore |
Glob patterns to exclude from linting |
--ui |
Launch browser UI to view violations |
--addr |
Interface to bind the UI server (default: 0.0.0.0, all interfaces; set localhost to restrict to this machine) |
--no-cache |
Disable caching/debounce |
--timeout |
Timeout per linter (default: 5m) |
--dry-run |
Print linter commands without executing |
Config-file-gated linters only run when a usable config or native project signal is discovered, unless explicitly named. For betterleaks that includes native config files (.betterleaks.toml / .gitleaks.toml) and any existing paths from secrets.configs across layered .gavel.yaml files. React Doctor runs when package.json declares React or a common React framework/plugin, and can also be enabled with doctor.config.*, legacy react-doctor.config.json, or package.json#reactDoctor. Disable betterleaks entirely via secrets.disabled: true in .gavel.yaml.
Inspect every supported test source without running test bodies:
gavel test outline
gavel test outline --framework jest,playwright
gavel test outline --framework fixture --fixture-files 'examples/*.fixture.md'The default outline includes Go test, Ginkgo, Jest, Vitest, Playwright, and Markdown fixtures. Positional paths constrain discovery, while --fixture-files overrides fixture globs from .gavel.yaml. Use gavel fixtures outline when you need fixture-specific sections, tables, AI criteria, and kind counts rather than the unified test view.
Run declarative tests defined in markdown files using tables, command blocks, or standalone code blocks.
gavel fixtures tests.md
gavel fixtures fixtures/**/*.md
gavel fixtures -v tests.md # verbose (stderr on pass, stdout+stderr on fail)
gavel fixtures -vv tests.md # more verbose
gavel fixtures --no-progress tests.md # disable progress display
gavel fixtures outline fixtures/**/*.md # parse and summarize without executingUse gavel fixtures outline when you want to inspect fixture files, sections,
tables, runner steps, AI checks, and source locations without running any build,
daemon, command, test/lint, skip, or AI work.
The same runner can be used from Go tests. Import the default fixture types when
using exec fixtures:
import (
"testing"
"github.com/flanksource/gavel/fixtures"
_ "github.com/flanksource/gavel/fixtures/types"
)
func TestFixtures(t *testing.T) {
runner, err := fixtures.NewRunner(fixtures.RunnerOptions{
Paths: []string{"fixtures/**/*.md"},
})
if err != nil {
t.Fatal(err)
}
runner.RunTesting(t)
}RunTesting(t) mirrors markdown files, headings, tables, and table rows as
nested t.Run nodes when passed a *testing.T. Use RunGomega(gomega.NewWithT(t))
for aggregate Gomega assertions, RunGinkgo() inside an existing Ginkgo It,
or RegisterGinkgoSpecs() during Ginkgo spec registration when fixture files,
sections, and rows should become Describe/It nodes.
Fixtures support three formats in a single markdown file:
Table format (preferred) — each row is a test. Custom columns become template variables in exec/args:
---
exec: bash
args: ["-c", "curl {{.flags}} {{.baseUrl}}{{.path}}"]
baseUrl: https://api.example.com
flags: "-s"
---
| Name | path | CEL |
|------------|---------|--------------------------|
| get users | /users | json.size() > 0 |
| get health | /health | json.status == "ok" |Command blocks — use when commands are multi-line or need per-test setup:
### command: my test
```yaml
exitCode: 0
env:
KEY: value
```
```bash
echo "hello"
```
* contains: helloStandalone code blocks — auto-detected from headings:
## Smoke Tests
```bash
echo "auto-detected"
```
* cel: stdout.contains("auto-detected")Front-matter reference
File-level front-matter applies to all tests. Fields marked with † can also be set per-test via table columns or command block YAML.
---
build: go build -o myapp # run once before all tests
exec: ./myapp # † default executable (default: bash)
args: [--verbose] # † default arguments
env: # † environment variables
LOG_LEVEL: debug
cwd: ./testdir # † working directory (relative to fixture file)
terminal: pty # † pseudo-terminal mode (merges stdout/stderr)
files: "**/*.go" # glob: replicate tests per matching file
codeBlocks: [bash, python] # languages to execute (default: [bash])
timeout: 30s # † total timeout
os: linux # † skip on other OSes (prefix ! to negate: !darwin)
arch: amd64 # † skip on other architectures
skip: "! command -v docker" # † skip if command exits 0
---Auto-injected variables (available as $VAR, {{.VAR}}, and env vars):
| Variable | Description |
|---|---|
GIT_ROOT_DIR |
Nearest parent directory containing .git |
GO_ROOT_DIR |
Nearest parent directory containing go.mod |
ROOT_DIR |
GIT_ROOT_DIR if available, else GO_ROOT_DIR, else working directory |
CWD |
Resolved working directory for the test |
GOOS |
Go runtime OS (e.g. linux, darwin) |
GOARCH |
Go runtime architecture (e.g. amd64, arm64) |
GOPATH |
Go workspace path |
CWD resolution priority: Test-level CWD → File-level CWD → SourceDir (directory of fixture file) → --cwd flag or current working directory.
See gavel fixtures --help for the full reference including CEL variables, validation shorthand, supported languages, and template syntax.
Example workflows — ready-made templates in examples/ that run against the bundled examples/sample-app; compose the yaml test / yaml lint steps and AI verification:
examples/precommit.fixture.md— lint + test only the files you changed, plus agofmtcheckexamples/pre-release.fixture.md— build, then the full test suite and every linterexamples/smoke-test.fixture.md— start the app, run fast smoke tests, and probe/healthexamples/ai-review.fixture.md— AI reviews the change against an acceptance-criteria checklist and scores it (needscaptain configure)
Run one with gavel fixtures examples/precommit.fixture.md, or copy it into your project.
Run Go benchmarks and compare base vs head results for regression detection.
# Run benchmarks and write JSON
gavel bench run ./pkg/... --out base.json
gavel bench run ./pkg/... --out head.json --count 10
# Compare results
gavel bench compare --base base.json --head head.json
gavel bench compare --base base.json --head head.json --threshold 15 --uibench run flags:
| Flag | Default | Description |
|---|---|---|
--count |
6 |
Number of times to run each benchmark |
--timeout |
20m |
Test execution timeout |
--benchtime |
1s |
Duration per benchmark |
--out |
stdout | Write JSON results to file |
--pattern |
. |
Benchmark name regex |
--benchmem |
true |
Include memory allocation stats |
--extra |
Extra flags passed through to go test |
bench compare flags:
| Flag | Default | Description |
|---|---|---|
--base |
Path to base bench JSON (required) | |
--head |
Path to head bench JSON (required) | |
--base-label |
base |
Display label for the base run |
--head-label |
head |
Display label for the head run |
--threshold |
10 |
Regression threshold in percent |
--ui |
false |
Launch browser UI with the comparison |
--addr |
0.0.0.0 |
Interface to bind the UI server (all interfaces; set localhost to restrict to this machine) |
Generate a conventional commit message via LLM and run pre-commit hooks from .gavel.yaml.
gavel commit # LLM-generated message, staged changes
gavel commit -t # choose files in an interactive tree picker
gavel commit -i -s # stream one-line AI summaries into picker rows
gavel commit -A # stage everything, let the LLM split it into logical commits + a chore commit for lock/generated files
gavel commit --max-commits=3 # same as -A, capped at 3 logical commits (implies -A)
gavel commit -m "chore: bump dep" # explicit message, still run compatibility analysis
gavel commit --stage all --dry-run # stage everything, print message
gavel commit --force # skip hooks| Flag | Description |
|---|---|
--stage |
Which changes to commit: session (default — commits only the running agent's edits, resolving GAVEL_SESSION_ID/CLAUDE_SESSION_ID/CODEX_SESSION_ID, falling back to staged when none is set), staged, unstaged, all, or an explicit Claude/Codex session id |
-t / --tree, -i / --interactive |
Open an interactive tree picker over changed files; press / in the picker to filter by path, status, language, or scope |
-s / --summary |
With -i/-t, stream a one-line AI summary into each candidate file row while the picker remains interactive |
-A / --commit-all |
Stage all changes and ask the LLM to split them into logical commit groups (plus a separate chore commit for lock files / build artifacts / generated bundles). Each group is committed as soon as its message is ready |
--max-commits |
Cap the number of logical commits, excluding the chore commit (default 7). Setting it implies -A. Rendered into the grouping prompt's output schema as maxItems and enforced by captain's schemaStrictness=retry policy |
-m / --message |
Explicit commit message; skips only the message-generation LLM call |
--model |
Override LLM model for commit-message/PR generation from .gavel.yaml commit.message.model (falling back to ai.model); accepts a compact mode:model:effort selector |
--group-model |
Override LLM model for AI grouping (-A) from .gavel.yaml commit.grouping.model (capable/sonnet-class); falls back to --model |
--dry-run |
Print the generated message without committing |
--force |
Skip pre-commit hooks |
--no-cache |
Bypass the LLM response cache |
--precommit |
How to handle gitignore + linked-dependency precommit checks: prompt, fail, skip, or false |
Pre-commit hooks are configured in .gavel.yaml under commit.hooks — see Configuration. Combined precommit behavior is controlled by .gavel.yaml commit.precommit.mode.
Show GitHub Actions status for a pull request.
gavel pr status
gavel pr status 42
gavel pr status https://github.com/owner/repo/pull/123
gavel pr status --follow --interval 30s
gavel pr status --follow --actions 'CI / Test' --fail-fast
gavel pr status --follow --comments '@coderabbit'
gavel pr status --logs --tail-logs 50| Flag | Description |
|---|---|
-R / --repo |
GitHub repository (owner/repo) |
--follow |
Keep watching until the dimensions you filtered on settle (see below) |
--fail-fast |
With --follow, return at the first definitive failure instead of waiting for the rest |
--interval |
Poll interval (default: 30s) |
--logs |
Fetch and include failed job logs (uses extra API quota) |
--tail-logs |
Number of failed log lines to show per step (default: 100) |
--comments |
Filter review comments by MatchItem patterns over comment ID and @author/@bot tokens |
--actions |
Filter workflow actions by MatchItem patterns over run/workflow ID, YAML path, workflow name, or job/check name |
--follow gates on the dimensions you actually filtered on, ANDed together: with
--actions it waits for the selected checks to complete, with --comments it waits for
the selected review threads to be resolved, and with neither it waits for the whole
rollup. A selector that matches nothing is an error, not a completion.
A PR GitHub reports as CONFLICTING renders a Merge conflicts section listing every
conflicting path, how it conflicts, and the commands that clear it — replayed locally with
git merge-tree, since no GitHub API returns the paths. The exit code is 1 whenever the
PR cannot merge: a failing check, a failing job, a failing gavel shard, or a merge conflict.
List pull requests with filtering, a live browser dashboard, and macOS menu bar indicator.
gavel pr list
gavel pr list --ui # live dashboard in browser
gavel pr list --menu-bar # macOS menu bar status indicator
gavel pr list --state merged --since 30d
gavel pr list --any --status # all authors, with check status
gavel pr list owner/repo another/repo # specific repos
gavel pr list --all --org myorg # all repos in org| Flag | Description |
|---|---|
--author |
GitHub username (default: @me) |
--since |
Show PRs updated since (e.g. 7d, now-30d, 2024-01-01) |
--state |
PR state: open (default), closed, merged, all |
--all |
List PRs across all repos in the org |
--any |
Show PRs from all authors |
--bots |
Include bot-authored PRs |
--org |
GitHub org for --all (auto-detected from git remote) |
--limit |
Maximum PRs to return (default: 50) |
--status |
Show GitHub Actions check status counts |
--logs |
Fetch failed job log tails (requires --status -v) |
--url |
Show PR URL instead of number |
--ui |
Open PR dashboard in browser with live updates |
--menu-bar |
Show macOS menu bar status indicator |
--interval |
Poll interval for --ui/--menu-bar (default: 60s) |
--addr |
Interface to bind the --ui/--menu-bar server (default: 0.0.0.0, all interfaces; set localhost to restrict to this machine) |
--port |
UI port (default: 9092, 0 to auto-scan upward for a free port) |
Retrieve and filter commit history from a git repository.
gavel git history --path .
gavel git history --since 2024-01-01 --until 2024-06-01
gavel git history --author "alice" --message "feat*"
gavel git history abc1234 # specific commit
gavel git history main..feature-branch # commit range| Flag | Description |
|---|---|
--path |
Path to git repository (default: .) |
--since |
Start date for filtering |
--until |
End date for filtering |
--author |
Filter by author name/email (repeatable) |
--message |
Filter by commit message |
--show-patch |
Include diffs |
Analyze commits with scope/technology detection, Kubernetes resource change tracking, severity scoring, and optional AI-powered analysis.
gavel git analyze
gavel git analyze --ai --ai-model api:haiku
gavel git analyze --scope backend --tech kubernetes
gavel git analyze --summary --summary-window week
gavel git analyze --include bots --exclude merges --verbose
gavel git analyze --input previous-run.json # re-analyze from JSON| Flag | Description |
|---|---|
--ai |
Enable AI-powered analysis |
--model |
AI model to use |
--scope |
Filter by scope types |
--commit-types |
Filter by conventional commit types |
--tech |
Filter by technologies |
--summary |
Generate a tree-based summary |
--summary-window |
Grouping window: day, week, month, year |
--include |
Include named filter sets from .gitanalyze.yaml |
--exclude |
Exclude named filter sets from .gitanalyze.yaml |
--verbose |
Show what was skipped and why |
--input |
Load from previous JSON output (repeatable) |
--short |
Show condensed file-change summary |
Create a .gitanalyze.yaml with sensible defaults, then optionally spawn an AI CLI to analyze the repo and recommend additional rules.
gavel git init-config # create defaults + AI recommendations via claude
gavel git init-config --model gemini # use gemini instead
gavel git init-config --model codex # use codex instead
gavel git init-config --model none # just create the defaults file, no AI| Flag | Description |
|---|---|
--path |
Path to git repository (default: .) |
--model |
AI CLI to use: claude, gemini, codex, or none (default: claude) |
--debug |
Enable debug logging |
Interactively improve commit messages using AI. Rewrites commits below a quality score threshold.
gavel git amend-commits
gavel git amend-commits --threshold 5.0 --base main
gavel git amend-commits --dry-runStart an SSH server that accepts git push and runs gavel test --lint. Developers add it as a git remote for local CI-like flows with real-time streaming results.
# Start the server
gavel ssh serve --port 2222
# From any project, add as a remote and push
git remote add gavel ssh://localhost:2222/myproject
git push gavel HEAD:mainResults stream back in real-time. Push is rejected on test failure. Repos are cached for fast incremental pushes.
| Flag | Description |
|---|---|
--port |
SSH server port (default: 2222) |
--host |
Listen address (default: 0.0.0.0) |
--host-key |
Path to SSH host key (default: ~/.gavel/ssh_host_key) |
--repo-dir |
Directory for cached bare repos (default: ~/.gavel/repos) |
The command run on push defaults to gavel test --lint but can be overridden via ssh.cmd in .gavel.yaml.
Install and enable a systemd unit for the SSH server (Linux only).
gavel ssh install # install and enable service
gavel ssh install --dry-run # preview without making changes
gavel ssh install --port 3333 --user gavel| Flag | Description |
|---|---|
--port |
SSH server port (default: 2222) |
--host |
Listen address (default: 0.0.0.0) |
--user |
System user to run the service as (default: gavel) |
--unit-path |
Path to write the systemd unit (default: /etc/systemd/system/gavel-ssh.service) |
--data-dir |
Directory for host key and cached repos (default: /var/lib/gavel) |
--binary |
Path to the gavel binary (default: current executable) |
--dry-run |
Print actions without writing |
--force |
Overwrite an existing unit file |
Build a compact markdown PR-comment summary from a gavel test/lint JSON result file.
gavel summary --input gavel-results.json
gavel summary --input results.json --output summary.md| Flag | Description |
|---|---|
--input |
Path to gavel JSON result file |
--output |
Path to write compact markdown (default: stdout) |
Run a standalone UI server for replaying a previously captured test run.
gavel ui serve run.json
gavel ui serve run.json other-run.json --auto-stop=10m --idle-timeout=5m| Argument / Flag | Description |
|---|---|
run.json [other-run.json ...] |
One or more JSON snapshots to load and merge in order |
--port |
Bind this port (0 = pick ephemeral) |
--addr |
Interface to bind (default: 0.0.0.0, all interfaces; set localhost to restrict to this machine) |
--auto-stop |
Hard wall-clock deadline from process start (default: 30m) |
--idle-timeout |
Exit after this long with no HTTP requests (default: 5m) |
--url-file |
Write the bound URL to this path for scripting |
View the merged architecture configuration for a repository path.
gavel repomap view .
gavel repomap get src/main.goView the merged .gavel.yaml for a path.
Interactive output shows merged YAML with comments for non-git-root sources.
Redirected output, --yaml, and --json emit only the merged config.
Pass --resolve (-r) to expand all registered AI prompts, including inline
Captain specs and file-backed templates, and show both declared and effective
model/backend details. With --json or --yaml, resolved output is wrapped as
{config, prompts}.
gavel config
gavel config ./pkg/api
gavel config ./cmd/gavel/main.go
gavel config --yaml ./cmd/gavel/main.go
gavel config --resolve ./cmd/gavel/main.go
gavel config --resolve --json
gavel config > merged.gavel.yamlResolution order is: built-in defaults, ~/.gavel.yaml, <git-root>/.gavel.yaml,
and <target-dir>/.gavel.yaml (or the parent directory when the target is a file).
Manage and execute native PostgreSQL TODO issues with Claude or Codex.
Every todo moves through a lifecycle: an ordered set of steps, each a
captain prompt reference plus a CEL when predicate (deciding whether the
step applies now) and ordered outcomes (deciding which status a finished run
lands the todo in). review and ask are human-facing statuses, not steps —
they wait on gavel todos plan approve|reject|revise or an answer to the
agent's questions. See MANUAL.md for the full model
and the retired-flags table.
gavel todos list
gavel todos list --status pending
gavel todos run 3f2a1b
gavel todos steps 3f2a1b # which lifecycle steps apply to this todo now
gavel todos run --step plan # propose a reviewable plan first (read-only)
gavel todos check 3f2a1b # run the TODO's complete definition of done--step names the lifecycle step to run (plan, verify, run, triage, or
any step the project's lifecycle declares); empty lets the lifecycle pick the
next applicable step. --model selects the model, in the compact
mode:model:effort form (cli:opus:high, api:sonnet).
The configured checks: test/lint suite is part of every todo's definition of done: when .gavel.yaml checks.enabled (or a todo's own checks: front matter) turns it on, the run step executes it after the agent reports done and feeds any failures back into the same session until they pass (bounded by todos.run.workflow.verify.maxIterations).
gavel todos check is the lifecycle's verify step run standalone: the
same fixture/CEL definition of done used by the implementation loop —
configured test/lint checks, the issue's persisted ## Verification fixture,
and any acceptance-criteria checklist step.
The old --driver, --mode, --prompt, and --group-by flags on todos run
no longer exist; see MANUAL.md for their replacements.
Many commands (test, lint, bench compare, pr status, pr list) support the --format flag for structured output:
# Single format
gavel test --format json
# Multiple formats written simultaneously
gavel test --format "json=results.json,html=report.html"
# Used by the GitHub Action internally
gavel test --lint --format "json=gavel-results.json,html=gavel-results.html"Supported formats: json, html, text (default).
Project-level configuration placed at the repository root. Gavel also loads ~/.gavel.yaml for user-level defaults and merges them (repo settings win).
Use gavel config [path] to inspect the merged result for any directory or file path.
A full annotated example lives in gavel.yaml.example and is also rendered in gavel config --help.
verify:
model: claude # AI model for generic AI verification fixtures
checks:
disabled: [SEC-1, PERF-2] # disable specific check IDs
disabledCategories: [performance] # disable entire categories
lint:
ignore:
- rule: SA1000 # ignore a specific rule
source: golangci-lint
- source: eslint # ignore all eslint violations in vendor
file: "vendor/**"
linters:
jscpd:
enabled: true # opt in to jscpd duplicate detection
react-doctor:
enabled: true # opt in without React detection or doctor.config.*
commit:
message:
model: claude-haiku-4-5 # fast model for commit-message generation
grouping:
model: claude-sonnet-4-5 # capable model for AI commit grouping (-A)
hooks: # pre-commit hooks
- name: lint-staged
run: "golangci-lint run --new-from-rev=HEAD~1"
files: ["*.go"]
precommit:
mode: prompt # prompt|fail|skip|false for gitignore + linked-deps checks
compatibility:
mode: prompt # prompt|fail|skip|false for removed-functionality / compatibility warnings
fixtures:
enabled: true # auto-discover fixture files during gavel test
files: ["tests/**/*.fixture.md"] # override default glob (**/*.fixture.md)
checks: # post-completion loop inside `gavel todos run`
enabled: true # run tests/lint after the agent is done, feed failures back
test: # omit to skip tests
changed: true # only packages affected by the agent's changes
lint: # omit to skip linting
changed: true # only new violations vs the base ref
ssh:
cmd: "gavel test --lint --fixtures" # override the command run on git push
pre: # hooks run before tests
- name: generate
run: "go generate ./..."
post: # hooks run after tests (non-blocking)
- name: cleanup
run: "rm -rf tmp/"
secrets:
disabled: false # set true to disable betterleaks entirely
configs: ["custom-leaks.toml"] # additional betterleaks/gitleaks config filesPlaced at the repository root (or any directory — gavel walks up to find it). Defines scope classification, technology detection, severity rules, and build/git settings.
scopes:
rules:
backend:
- path: "cmd/**"
- path: "pkg/**"
frontend:
- path: "web/**"
tech:
rules:
kubernetes:
- path: "deploy/*.yaml"
severity:
default: medium
rules:
'kubernetes.kind == "Secret"': critical
'change.type == "deleted"': critical
'commit.line_changes > 500': critical
git:
version_field_patterns:
- "**.image"
- "**.tag"
- "**.version"Embedded defaults detect common scope types (ci, dependency, docs, test) and technologies (Go, Node.js, Python, Kubernetes, Terraform, Docker, etc.).
Controls which commits, files, authors, and resources are excluded from gavel git analyze. Placed at the repository root.
# Named filter sets toggled with --include / --exclude
filter_sets:
bots:
ignore_authors:
- "dependabot*"
- "renovate*"
- "github-actions*"
noise:
ignore_files:
- "*.lock"
- "go.sum"
- "package-lock.json"
generated:
ignore_resources:
- kind: ConfigMap
name: "*-generated"
- kind: Secret
# Which filter sets are active by default
includes:
- bots
- noise
# Top-level filters (always applied)
ignore_commits:
- "fixup!*"
- "squash!*"
ignore_files:
- ".idea/*"
- "*.svg"
ignore_commit_types:
- "chore"
- "ci"
# CEL expressions — skip when true
ignore_commit_rules:
- cel: "commit.is_merge"
- cel: "commit.line_changes > 10000"
# Kubernetes resource filters
ignore_resources:
- kind: Secret
- kind: ConfigMap
name: "*-generated"CEL variables for ignore_commit_rules
| Variable | Type | Description |
|---|---|---|
commit.author |
string | Author name |
commit.author_email |
string | Author email |
commit.subject |
string | Commit subject line |
commit.body |
string | Commit body |
commit.type |
string | Conventional commit type |
commit.scope |
string | Conventional commit scope |
commit.is_merge |
bool | True if subject starts with "Merge " |
commit.files_changed |
int | Number of files changed |
commit.line_changes |
int | Total lines added + deleted |
commit.additions |
int | Lines added |
commit.deletions |
int | Lines deleted |
commit.files |
list | File paths changed |
commit.tags |
list | Commit tags |
commit.is_tagged |
bool | True if commit has tags |
Embedded defaults skip bot authors (dependabot*, renovate*, github-actions*), lock files (*.lock, go.sum, etc.), and merge commits.
Override at the CLI:
gavel git analyze --exclude bots # include bot commits
gavel git analyze --include generated # activate the "generated" filter set
gavel git analyze --verbose # show skip reasonsGavel ships Agent Skills that teach AI coding agents to reach for gavel across the workflow: writing data-driven tests, driving the everyday test+lint loop, using gavel instead of gh/git for PRs and commits, running the TODO/AI-review loop, and migrating CI pipelines onto the gavel composite action. Skills are auto-discovered from .agents/skills/ by any compatible agent (Claude Code, VS Code Copilot, Cursor, Gemini CLI, and others).
| Skill | What it teaches the agent |
|---|---|
gavel-fixture-tester |
Author fixture-based tests in markdown — YAML front-matter, tables, command blocks, and CEL assertions for stdout/stderr/exitCode/json. |
gavel-runner |
Run gavel test and lint — focus on a subset (--changed, --cache, framework, runner pass-through), re-run only failures (--failed defaults to .gavel/last.json), suppress noise with baselines, pull JSON / markdown / HTML out via --format, attach to live runs through the UI server's HTTP+SSE API, and tune the four-layer timeout stack. |
gavel-git |
Use gavel over gh/git — gavel pr status for PR + CI status (replaces gh pr view/gh run view), gavel commit -p / gavel pr create to open PRs with AI-generated content, and gavel commit (session-scoped, conventional message, hooks) for commits. |
gavel-todos |
Run the TODO loop — have a coding agent implement or plan TODOs, then execute the same fixture/CEL definition of done manually with gavel todos check. |
gavel-ci-migrator |
Migrate CI — discover existing golangci-lint / go test / `make lint |
For Claude Code (marketplace):
# Add the Flanksource marketplace, then install the gavel-skills plugin
/plugin marketplace add flanksource/gavel
/plugin install gavel-skills@flanksource-gavelAfter installation, all five skills become available. The agent picks gavel-fixture-tester when you ask it to write a fixture, gavel-runner when you ask it to run tests, rerun failures, or inspect results, gavel-git when you ask it to check a PR, see why CI is failing, or commit / open a PR, gavel-todos when you ask it to run the todos or verify committed work, and gavel-ci-migrator when you ask it to switch CI to gavel or replace golangci-lint-action / go test jobs with the gavel action.
For any agent (via the open Skills CLI):
npx skills add flanksource/gavel # current project
npx skills add flanksource/gavel -g # all projects
npx skills add flanksource/gavel -l # preview before installingFor a local clone (auto-discovery):
Clone the repo and open it in your agent — .agents/skills/ is picked up automatically with no extra setup.
See .agents/skills/README.md for the full reference, including manual settings.json configuration and per-agent install paths.
task build # build binary (also: make build)
task test # run all tests
task test:unit # run unit tests only
task lint # run linters
task fmt # format code
task ci # fmt + lint + test + buildSee LICENSE for details.