Our review
Auto-discovers linting and formatting tools and generates the .claude/checkmate.json configuration file.
Strengths
- Automatic detection of tools based on the environment (npm, uv, cargo, etc.)
- Monorepo support with multiple environments
- Generates a ready-to-use configuration
Limitations
- Requires tools to be already installed in the project
- Only covers explicitly detected languages and tools (JS/TS, Python, Rust, Go)
- If a config already exists, user must confirm deletion
When initializing checkmate for a new project.
If a checkmate configuration already exists, use /checkmate:checkmate-refresh instead.
Security analysis
SafeThe skill only runs read-only diagnostic commands (ls, find, command -v, --version) to detect project structure and available tools. It does not execute any destructive actions or exfiltrate data. All commands are standard and safe.
No concerns found
Examples
Set up code quality checks for this project using checkmate.Auto-discover all linting and formatting tools in this project and create the checkmate configuration.Run checkmate-init to generate a .claude/checkmate.json that covers all packages in this monorepo.name: checkmate-init description: Auto-discover linting and formatting tools, generate .claude/checkmate.json configuration. Use when setting up checkmate for a new project. disable-model-invocation: true user-invocable: true argument-hint: ""
checkmate-init
Configure code quality checks for this project by discovering available tools and creating .claude/checkmate.json.
Instructions
You are helping the user set up automated code quality checks. Follow this process:
Step 0: Check for Existing Config
First, check if a config already exists:
ls -la .claude/checkmate.json 2>/dev/null
If config exists, STOP and warn the user:
A checkmate configuration already exists at .claude/checkmate.json
Running /checkmate:checkmate-init will delete the existing config and create a new one.
If you want to update the existing config, run /checkmate:checkmate-refresh instead.
Do you want to proceed and replace the existing configuration? (yes/no)
Only proceed if user explicitly confirms. If they say no, suggest /checkmate:checkmate-refresh.
Step 1: Discover Project Structure
Run these commands to understand the project:
# Find project root (look for .git, package.json, pyproject.toml, Cargo.toml)
ls -la
# Detect file types in the project
find . -type f -name "*.py" | head -1
find . -type f -name "*.ts" -o -name "*.tsx" | head -1
find . -type f -name "*.js" -o -name "*.jsx" | head -1
find . -type f -name "*.rs" | head -1
find . -type f -name "*.go" | head -1
find . -type f \( -name "*.c" -o -name "*.cpp" -o -name "*.cc" -o -name "*.h" -o -name "*.hpp" \) | head -1
find . -type f -name "*.sh" | head -1
# Check for formatter-compatible files
find . -type f \( -name "*.json" -o -name "*.md" -o -name "*.yaml" -o -name "*.yml" \) | head -1
# Check for package managers and config files
ls package.json pyproject.toml Cargo.toml go.mod CMakeLists.txt Makefile .clang-format 2>/dev/null
# Detect build/temp directories to exclude
ls -d dist build out .next .nuxt coverage __pycache__ .pytest_cache target 2>/dev/null
Step 2: Detect All Environments
Use the checkmate:detect-environment subagent to detect all package managers and environment configurations, including nested setups in monorepos.
The agent returns an array of environments:
{
"environments": [
{
"path": ".",
"javascript": { "primary": "pnpm", "exec": ["pnpm", "exec"] },
"python": null,
"rust": null,
"go": null
},
{
"path": "services/api",
"javascript": null,
"python": { "primary": "uv", "exec": ["uv", "run"] },
"rust": null,
"go": null
}
]
}
Use the exec array to build tool invocation commands:
exec: ["pnpm", "exec"]→{ "command": "pnpm", "args": ["exec", "prettier", "--check", "$FILE"] }exec: ["uv", "run"]→{ "command": "uv", "args": ["run", "ruff", "check", "$FILE"] }exec: [](empty) →{ "command": "golangci-lint", "args": ["run", "$FILE"] }
For monorepos: Each environment path needs its own check configuration with the correct invocation pattern.
Step 3: Discover Available Tools
Using the detected invocation pattern, check which tools are available:
JavaScript/TypeScript tools (using detected exec pattern):
<exec> prettier --version 2>/dev/null && echo "prettier available"
<exec> eslint --version 2>/dev/null && echo "eslint available"
<exec> biome --version 2>/dev/null && echo "biome available"
<exec> tsc-files --version 2>/dev/null && echo "tsc-files available"
Note: Avoid tsc - it checks the entire project on every file change. Use tsc-files (per-file) or eslint with @typescript-eslint instead.
Python tools (using detected exec pattern):
<exec> ruff --version 2>/dev/null && echo "ruff available"
<exec> ty --version 2>/dev/null && echo "ty available"
<exec> mypy --version 2>/dev/null && echo "mypy available"
Rust tools:
cargo fmt --version 2>/dev/null && echo "rustfmt available"
cargo clippy --version 2>/dev/null && echo "clippy available"
Go tools:
command -v golangci-lint && golangci-lint --version
command -v staticcheck && staticcheck --version
C/C++ tools:
# Check standard paths first
command -v clang-format && clang-format --version
command -v clang-tidy && clang-tidy --version
command -v cppcheck && cppcheck --version
# macOS Homebrew LLVM (not in PATH by default)
/opt/homebrew/opt/llvm/bin/clang-format --version 2>/dev/null
/opt/homebrew/opt/llvm/bin/clang-tidy --version 2>/dev/null
# Linux Homebrew LLVM
/home/linuxbrew/.linuxbrew/opt/llvm/bin/clang-format --version 2>/dev/null
Platform note: On macOS with Homebrew, LLVM tools are installed at /opt/homebrew/opt/llvm/bin/ but not symlinked to avoid conflicts with Apple's clang. Use the full path in commands:
{ "command": "/opt/homebrew/opt/llvm/bin/clang-format", "args": ["--dry-run", "-Werror", "$FILE"] }
Shell script tools:
command -v shellcheck && shellcheck --version
command -v shfmt && shfmt --version
If no tools found: Suggest installation based on detected environment:
- Python + uv:
uv add --dev ruff - Python + pip:
pip install ruff - TypeScript + pnpm:
pnpm add -D prettier eslint typescript - TypeScript + npm:
npm install -D prettier eslint typescript - TypeScript (tsc present, tsc-files missing):
pnpm add -D tsc-filesornpm install -D tsc-files - Go:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - C/C++ (macOS):
brew install llvm(note: use full path/opt/homebrew/opt/llvm/bin/clang-format) - C/C++ (Linux):
apt install clang-format clang-tidyordnf install clang-tools-extra - Shell:
brew install shellcheck shfmtorapt install shellcheck shfmt
Step 4: Build Configuration
Based on discovered tools, file types, and detected invocation pattern, propose a checkmate.json configuration.
Schema (array format):
{
"environments": [
{
"name": "<environment name>",
"paths": ["<path1>", "<path2>"],
"exclude": ["<pattern>"],
"checks": {
"<extension>": [
{
"name": "<display name>",
"command": "<executable>",
"args": ["<arg1>", "$FILE"],
"parser": "<parser name or object>",
"maxDiagnostics": 5
}
]
}
}
]
}
Field definitions:
name- Optional descriptive name for the environmentpaths- Array of paths this environment covers (use"."for root)exclude- Optional array of glob patterns to exclude (e.g.,"**/*.test.ts")checks- Object mapping file extensions to check arrays$FILE- Placeholder replaced with actual file path at runtime
Default excludes: Always exclude build artifacts and temp directories that exist in the project:
- JavaScript/TypeScript:
dist/**,build/**,out/**,.next/**,.nuxt/**,coverage/** - Python:
__pycache__/**,.pytest_cache/**,.venv/**,*.egg-info/** - Rust:
target/** - General:
node_modules/**(already excluded by most tools)
Matching rule: First environment in the array that matches wins. Put specific environments before general ones.
Simple project (single environment):
{
"environments": [
{
"name": "root",
"paths": ["."],
"exclude": ["dist/**", "coverage/**"],
"checks": {
".ts,.tsx": [
{ "name": "prettier", "command": "pnpm", "args": ["exec", "prettier", "--check", "$FILE"], "parser": "prettier", "_auto": true }
]
}
}
]
}
Note: Only include excludes for directories that actually exist in the project.
Monorepo (multiple environments):
{
"environments": [
{
"name": "frontend",
"paths": ["apps/web", "packages/ui"],
"checks": {
".ts,.tsx": [
{ "name": "prettier", "command": "pnpm", "args": ["exec", "prettier", "--check", "$FILE"], "parser": "prettier", "_auto": true }
]
}
},
{
"name": "api",
"paths": ["services/api"],
"checks": {
".py": [
{ "name": "ruff", "command": "uv", "args": ["run", "ruff", "check", "$FILE"], "parser": "ruff", "_auto": true }
]
}
},
{
"name": "root",
"paths": ["."],
"exclude": ["apps/**", "packages/**", "services/**"],
"checks": {
".md,.json": [
{ "name": "prettier", "command": "pnpm", "args": ["exec", "prettier", "--check", "$FILE"], "parser": "prettier", "_auto": true }
]
}
}
]
}
Important: Array order matters - first matching environment wins. Put specific environments (like services/api) before general ones (like .).
Predefined parsers:
| Parser | Use For | Output Format |
|--------|---------|---------------|
| ruff | Ruff linter | path:line:col: CODE message |
| ty | ty type checker | Multi-line Rust-style errors |
| eslint | ESLint (with @typescript-eslint for type checks) | path:line:col severity message rule |
| tsc | tsc, tsc-files (TypeScript) | path(line,col): error TScode: message |
| biome | Biome | path:line:col rule message |
| prettier | Any format checker | Pass/fail only (non-empty = fail) |
| jsonl | JSON Lines output | {"file":"x.ts","line":10,"message":"err"} |
| gcc | GCC-style output | path:line:col: severity: message (clang-format, clang-tidy, shellcheck --format=gcc) |
| generic | Fallback | Returns raw output truncated |
Custom regex parser: For tools without a predefined parser, use an inline regex with named capture groups:
{
"name": "my-linter",
"command": "my-linter",
"args": ["check", "$FILE"],
"parser": {
"pattern": ":(?<line>\\d+):(?<column>\\d+):\\s*(?<message>.+)",
"severity": "error"
}
}
Named groups: line, column, message, rule, severity (all optional).
For unfamiliar tools, use the checkmate:configure-tool subagent to analyze output and build a regex.
Common configurations by environment:
All auto-discovered checks include "_auto": true so /checkmate:checkmate-refresh can manage them.
Python (adapt runner to detected environment):
// uv (uv.lock present)
{ "name": "ruff", "command": "uv", "args": ["run", "ruff", "check", "$FILE"], "parser": "ruff", "_auto": true }
// poetry (poetry.lock present)
{ "name": "ruff", "command": "poetry", "args": ["run", "ruff", "check", "$FILE"], "parser": "ruff", "_auto": true }
// pipenv (Pipfile present)
{ "name": "ruff", "command": "pipenv", "args": ["run", "ruff", "check", "$FILE"], "parser": "ruff", "_auto": true }
// global install or activated venv
{ "name": "ruff", "command": "ruff", "args": ["check", "$FILE"], "parser": "ruff", "_auto": true }
TypeScript/JavaScript (adapt runner to detected package manager):
// pnpm (pnpm-lock.yaml present)
{ "name": "eslint", "command": "pnpm", "args": ["exec", "eslint", "$FILE"], "parser": "eslint", "_auto": true }
// npm (package-lock.json present)
{ "name": "eslint", "command": "npx", "args": ["eslint", "$FILE"], "parser": "eslint", "_auto": true }
// yarn (yarn.lock present)
{ "name": "eslint", "command": "yarn", "args": ["eslint", "$FILE"], "parser": "eslint", "_auto": true }
// bun (bun.lockb present)
{ "name": "eslint", "command": "bun", "args": ["eslint", "$FILE"], "parser": "eslint", "_auto": true }
Rust (always use cargo):
{ "name": "clippy", "command": "cargo", "args": ["clippy", "--", "-D", "warnings"], "parser": "generic", "_auto": true }
{ "name": "rustfmt", "command": "cargo", "args": ["fmt", "--check"], "parser": "prettier", "_auto": true }
Go (direct invocation):
{ "name": "golangci-lint", "command": "golangci-lint", "args": ["run", "$FILE"], "parser": { "pattern": ":(?<line>\\d+):(?<column>\\d+):\\s*(?<message>.+)", "severity": "error" }, "_auto": true }
C/C++ (use full path on macOS Homebrew):
// clang-format (check formatting)
{ "name": "clang-format", "command": "clang-format", "args": ["--dry-run", "-Werror", "$FILE"], "parser": "gcc", "_auto": true }
// macOS Homebrew (not in PATH)
{ "name": "clang-format", "command": "/opt/homebrew/opt/llvm/bin/clang-format", "args": ["--dry-run", "-Werror", "$FILE"], "parser": "gcc", "_auto": true }
// clang-tidy (static analysis)
{ "name": "clang-tidy", "command": "clang-tidy", "args": ["$FILE", "--"], "parser": "gcc", "_auto": true }
// cppcheck
{ "name": "cppcheck", "command": "cppcheck", "args": ["--error-exitcode=1", "--template=gcc", "$FILE"], "parser": "gcc", "_auto": true }
Shell scripts:
// shellcheck (with gcc output format for line numbers)
{ "name": "shellcheck", "command": "shellcheck", "args": ["--format=gcc", "$FILE"], "parser": "gcc", "_auto": true }
// shfmt (format check)
{ "name": "shfmt", "command": "shfmt", "args": ["-d", "$FILE"], "parser": "prettier", "_auto": true }
Full example (Python + uv):
{
"environments": [
{
"name": "root",
"paths": ["."],
"exclude": ["__pycache__/**", ".pytest_cache/**", ".venv/**"],
"checks": {
".py": [
{ "name": "ruff format", "command": "uv", "args": ["run", "ruff", "format", "--check", "$FILE"], "parser": "prettier", "_auto": true },
{ "name": "ruff check", "command": "uv", "args": ["run", "ruff", "check", "$FILE"], "parser": "ruff", "_auto": true },
{ "name": "ty", "command": "uv", "args": ["run", "ty", "check", "$FILE"], "parser": "ty", "_auto": true }
]
}
}
]
}
Full example (TypeScript + npm):
{
"environments": [
{
"name": "root",
"paths": ["."],
"exclude": ["dist/**", "build/**", "coverage/**"],
"checks": {
".ts,.tsx": [
{ "name": "prettier", "command": "npx", "args": ["prettier", "--check", "$FILE"], "parser": "prettier", "_auto": true },
{ "name": "eslint", "command": "npx", "args": ["eslint", "$FILE"], "parser": "eslint", "_auto": true },
{ "name": "tsc-files", "command": "npx", "args": ["tsc-files", "--noEmit", "$FILE"], "parser": "tsc", "_auto": true }
],
".json,.md": [
{ "name": "prettier", "command": "npx", "args": ["prettier", "--check", "$FILE"], "parser": "prettier", "_auto": true }
]
}
}
]
}
Note: For TypeScript type checking, use tsc-files (checks single files) instead of tsc (checks entire project). Alternatively, configure eslint with @typescript-eslint for type-aware linting.
Full example (C++ with CMake):
{
"environments": [
{
"name": "root",
"paths": ["."],
"exclude": ["build/**", "cmake-build-*/**", "third_party/**"],
"checks": {
".cpp,.cc,.c,.h,.hpp": [
{ "name": "clang-format", "command": "/opt/homebrew/opt/llvm/bin/clang-format", "args": ["--dry-run", "-Werror", "$FILE"], "parser": "gcc", "_auto": true }
],
".sh": [
{ "name": "shellcheck", "command": "shellcheck", "args": ["--format=gcc", "$FILE"], "parser": "gcc", "_auto": true },
{ "name": "shfmt", "command": "shfmt", "args": ["-d", "$FILE"], "parser": "prettier", "_auto": true }
]
}
}
]
}
Note: For C++ projects, use full paths for Homebrew-installed LLVM tools on macOS. The --dry-run -Werror flags make clang-format exit non-zero on formatting issues without modifying files.
Step 5: Gather User Preferences
Before presenting the configuration, use the AskUserQuestion tool to gather preferences. Ask all questions in a single call.
Question 1 — Test files:
- header: "Test files"
- question: "Should test files be included or excluded from checks?"
- options:
- "Exclude" — Add exclude patterns for test directories (tests/**, **/.test., **/.spec.)
- "Include" — Run all checks on test files too
- multiSelect: false
Question 2 — Additional checks:
- header: "Custom checks"
- question: "Any custom scripts or project-specific checks to add?"
- options:
- "None" — Only use discovered tools
- "Yes, I'll describe" — Pause for user to describe custom checks
- multiSelect: false
Question 3 — Coverage gaps:
- header: "Missing tools"
- question: "Any tools or file types I missed?"
- options:
- "Looks complete" — Proceed with discovered tools
- "Yes, I'll describe" — Pause for user to list additions
- multiSelect: false
Apply the answers to the proposed configuration before presenting it.
Decision guidance:
- biome vs prettier+eslint: biome is faster but less configurable. Use prettier+eslint for existing configs.
- maxDiagnostics: 5 is good default. Increase to 10 for strict projects. Set to 1 for slow tools.
- Check order: Format checks first, then lint, then type check (fastest to slowest).
- Related file types: Formatters like prettier handle more than code. Recommend adding checks for:
.json- package.json, tsconfig.json, config files.md- README, documentation.yaml,.yml- CI configs, docker-compose.css,.scss- stylesheets.html- templates
For unfamiliar tools, offer to use the checkmate:configure-tool subagent to analyze output and build a parser.
Step 6: Present and Confirm
Show the user:
- What file types were detected
- What tools were found
- The proposed configuration (incorporating answers from Step 5)
Use AskUserQuestion to confirm:
- header: "Config"
- question: "Write this configuration to .claude/checkmate.json?"
- options:
- "Yes, write it" — Proceed to write
- "Show me first" — Display full JSON before writing
- "Make changes" — User describes modifications
- multiSelect: false
Step 7: Write Configuration
After user confirmation, create .claude/checkmate.json with the agreed configuration.
Ensure the .claude/ directory exists:
mkdir -p .claude
Then write the configuration file. The validation hook runs automatically and flags any schema errors.
Step 8: Test Configuration
After writing, test with a sample file:
# Find a sample file and run its checks manually
find . -name "*.py" -o -name "*.ts" | head -1
Run one of the configured commands manually to verify it works.
Inform the user that the checkmate hook runs automatically on file edits. Suggest running /checkmate:checkmate-refresh periodically to keep the config up to date.
Notes
- The
$FILEplaceholder in args gets replaced with the actual file path - Comma-separated extensions (e.g.,
.ts,.tsx) apply the same checks to multiple types - The
parserfield determines how output is parsed into diagnostics - Use
genericparser for tools without a specific parser maxDiagnosticslimits output per check (default: 5)- Task reviewers are not auto-discovered; add
tasksarray manually to trigger code review agents after Task completions (see README)
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.