AgentSkillsCN

checkmate-init

自动发现代码检查与格式化工具,并生成 .claude/checkmate.json 配置文件。当用户为新项目设置 Checkmate 时,可使用此技能。

SKILL.md
--- frontmatter
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:

bash
ls -la .claude/checkmate.json 2>/dev/null

If config exists, STOP and warn the user:

code
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:

bash
# 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:

json
{
  "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):

bash
<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):

bash
<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:

bash
cargo fmt --version 2>/dev/null && echo "rustfmt available"
cargo clippy --version 2>/dev/null && echo "clippy available"

Go tools:

bash
command -v golangci-lint && golangci-lint --version
command -v staticcheck && staticcheck --version

C/C++ tools:

bash
# 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:

json
{ "command": "/opt/homebrew/opt/llvm/bin/clang-format", "args": ["--dry-run", "-Werror", "$FILE"] }

Shell script tools:

bash
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-files or npm 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-tidy or dnf install clang-tools-extra
  • Shell: brew install shellcheck shfmt or apt 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):

json
{
  "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 environment
  • paths - 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):

json
{
  "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):

json
{
  "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:

ParserUse ForOutput Format
ruffRuff linterpath:line:col: CODE message
tyty type checkerMulti-line Rust-style errors
eslintESLint (with @typescript-eslint for type checks)path:line:col severity message rule
tsctsc, tsc-files (TypeScript)path(line,col): error TScode: message
biomeBiomepath:line:col rule message
prettierAny format checkerPass/fail only (non-empty = fail)
jsonlJSON Lines output{"file":"x.ts","line":10,"message":"err"}
gccGCC-style outputpath:line:col: severity: message (clang-format, clang-tidy, shellcheck --format=gcc)
genericFallbackReturns raw output truncated

Custom regex parser: For tools without a predefined parser, use an inline regex with named capture groups:

json
{
  "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):

json
// 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):

json
// 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):

json
{ "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):

json
{ "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):

json
// 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:

json
// 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):

json
{
  "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):

json
{
  "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):

json
{
  "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:

  1. What file types were detected
  2. What tools were found
  3. 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:

bash
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:

bash
# 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 $FILE placeholder in args gets replaced with the actual file path
  • Comma-separated extensions (e.g., .ts,.tsx) apply the same checks to multiple types
  • The parser field determines how output is parsed into diagnostics
  • Use generic parser for tools without a specific parser
  • maxDiagnostics limits output per check (default: 5)
  • Task reviewers are not auto-discovered; add tasks array manually to trigger code review agents after Task completions (see README)