This is not an officially supported Google product. This project is not eligible for the Google Open Source Software Vulnerability Rewards Program.
Overview
This is not an officially supported Google product. This project is not eligible for the Google Open Source Software Vulnerability Rewards Program. A static analysis linter for Agent Skills to ensure they meet the specification in presubmit checks. This project is a Dart package and can be run as a CLI tool to validate your skills directory before committing. An is a portable, self-contained directory that extends an AI agent's capabilities. By default, skills_lint enforces the Agent Skills specification. We recommend starting with the configuration in the skills-lint-setup skill. skills_lint ships as both a standalone native binary (no Dart SDK required) and as a Dart package on pub.dev. Pick the path that matches your environment. If you already have the Dart SDK installed, the standard pub.dev paths still work and are unchanged. For multiple projects without per-project pubspec entries: The recommended path for CI runners and laptops without the Dart SDK on PATH.
README
skills_lint
This is not an officially supported Google product. This project is not eligible for the Google Open Source Software Vulnerability Rewards Program.
A static analysis linter for Agent Skills to ensure they meet the specification in presubmit checks. This project is a Dart package and can be run as a CLI tool to validate your skills directory before committing.
Table of Contents
Overview
An Agent Skill is a portable, self-contained directory that extends an AI agent’s capabilities.
By default, skills_lint enforces the
Agent Skills specification. We
recommend starting with the configuration in the
skills-lint-setup
skill.
Installation
skills_lint ships as both a standalone native binary (no Dart
SDK required) and as a Dart package on pub.dev. Pick the path that
matches your environment.
1. Dart developers — pub.dev
If you already have the Dart SDK installed, the standard pub.dev paths still work and are unchanged.
As a project dev_dependency
Add to your pubspec.yaml:
dev_dependencies:
skills_lint: ^0.5.3+1
Then:
dart pub get
Globally installed
For multiple projects without per-project pubspec entries:
dart install skills_lint
2. install.sh — Linux + macOS, no Dart required
The recommended path for CI runners and laptops without the Dart SDK
on PATH. Downloads the matching prebuilt binary from the latest GitHub
Release, verifies its SHA256, and installs to /usr/local/bin (with a
sudo fallback). It works on each platform that
the release
has a skills_lint-.tar.gz for, and names those platforms on any
other machine. The macOS binaries need macOS 14 or later; on older versions
it stops with an error.
curl -fsSL https://github.com/google/skills_lint.dart/releases/latest/download/install.sh | bash
Optional env vars (set before the bash part):
INSTALL_DIR— install destination (default/usr/local/bin).VERSION— pin a specific release like0.4.0(defaultlatest).REPO— alternate source repo (defaultgoogle/skills_lint.dart).
macOS first-launch note
macOS binaries are not yet code-signed. The first time you run the binary, macOS Gatekeeper will block it (“cannot be opened because the developer cannot be verified”). Remove the quarantine flag once:
xattr -d com.apple.quarantine "$(which skills_lint)"
This step goes away once notarized builds ship.
3. Direct download — Linux + macOS, no install script
For environments where piping a script to bash isn’t acceptable.
Grab the tarball for your platform from
the latest GitHub Release
and verify its SHA256 against the release’s SHA256SUMS asset.
TARGET="linux-x64" # the of a skills_lint-.tar.gz asset
VERSION="0.5.3+1"
BASE="https://github.com/google/skills_lint.dart/releases/download/skills_lint-v${VERSION}"
curl -fsSLO "${BASE}/skills_lint-${TARGET}.tar.gz"
curl -fsSLO "${BASE}/SHA256SUMS"
grep " skills_lint-${TARGET}.tar.gz$" SHA256SUMS | sha256sum -c -
tar -xzf "skills_lint-${TARGET}.tar.gz"
sudo install -m 0755 "skills_lint-${TARGET}" /usr/local/bin/skills_lint
The macOS binaries need macOS 14 or later. On macOS, replace
sha256sum -c - with shasum -a 256 -c -.
4. Homebrew — Linux + macOS, no Dart required
brew tap google/skills-lint https://github.com/google/skills_lint.dart
brew install google/skills-lint/skills_lint
Usage
skills_lint runs as a command-line tool, configured by flags or by
a skills_lint.yaml file. In a Dart package, you can also run it from
dart test with the validateSkills API. See
Dart test setup.
1. As a Command Line Tool with Arguments
Run the linter against your skills or root skills directories by passing arguments.
dart run skills_lint --skills-directory ./path/to/skills-root
Multiple root directories can be specified:
dart run skills_lint --skills-directory ./path/to/root-a --skills-directory ./path/to/root-b
Validate Individual Skills directly using --skill or -s:
dart run skills_lint --skill ./path/to/my-single-skill
If no directory is specified, it automatically checks .claude/skills and .agents/skills relative to your workspace root.
Flags
-
-d,--skills-directory: Specifies a root directory containing sub-folders of skills to validate. Can be passed multiple times. Can use home tilde expansion (ex:~/.agents/skills). -
-s,--skill: Specifies an individual skill directory to validate directly. Can be passed multiple times. -
-q,--quiet: Hide non-error validation output. -
-w,--print-warnings: Enable printing of warning messages. -
--fast-fail: Halt execution immediately on the error. -
--version: Print the skills_lint version and exit. All other flags are ignored. Arguments that fail to parse, such as an unknown flag or--format xml, exit with a usage error. -
-c,--config: Path to a configuration file. Defaults toskills_lint.yamlin the current directory. Paths declared inside a configuration file resolve relative to that file’s directory. -
--ignore-config: Ignore the YAML configuration file entirely. -
--ignore-file: Path to a JSON file listing lints to ignore for the run. Each entry names arule_idand afile_name. An optionalsubjectlimits the entry to the findings about one thing in that file;RULES.mdsays which rules set one, and in what format. -
--generate-baseline: Write every current error intoskills_lint_ignore.jsonso existing violations are ignored on future runs. Entries it can’t read are not carried over, and it prints a message naming the skills they belonged to. -
--[no-]check-trailing-whitespace: Enable/disable checking for trailing whitespace. (Disabled by default). -
--[no-]pub-archive-includes-skill: Check thatdart pub publishincludes each skill inskills/and the files it links to. (Disabled by default). -
--[no-]published-skill-name: Enable/disable checking that published package skills follow the package naming convention. (Disabled by default). -
--fix: Write fixes for failing lints to disk, then print the diagnostics that remain. The exit code reflects the diagnostics left after fixing. A fix is kept only if it lowers the skill’s errors, then warnings. Fixes repeat until they stop helping, up to 10 passes, so one run is usually enough. When a fix renames a skill directory and the rename can’t happen, for example because the target exists, the fixes that need the rename are skipped with the reason, and the other fixes still land. -
--dry-run: When combined with--fix, prints the proposed diff and the resulting diagnostics without writing. It checks each skill on its own, so it doesn’t show links in other skills that a rename would break.--fixchecks every skill again after a rename and reports them. A failure that only writing reveals, such as a missing permission, can make--fixskip a fix that--dry-runshowed, or leave the skill unfixed: whenSKILL.mdcan’t be written,--fixwrites none of that skill’s fixes, reports the error, and fails the run. -
--fix-apply: Deprecated alias for--fix. Prints a deprecation notice on use. -
--format: Output format for validation diagnostics. One of:text(default): human-readable terminal output.json: a machine-readable JSON array of validation results.sarif: a SARIF 2.1.0 document for CI and GitHub Code Scanning. See Recipe: GitHub Code Scanning.
Diagnostics go to standard output in every format, so redirect them to a file to capture a report:
skills_lint --format=sarif > skills-lint.sarif. Operational failures stay on standard error and never corrupt the report. Bugs in skills_lint start withskills_lint internal error:. Problems in your files, such as a--fixrename that can’t happen, start withError:.
2. As a Command Line Tool with a YAML Configuration File
You can configure the linter using a configuration file (defaulting to skills_lint.yaml in the current directory).
Create skills_lint.yaml in the root of your repository:
# skills_lint.yaml
skills_lint:
rules:
check-relative-paths: error
check-absolute-paths: error
directories:
- path: "~/.agents/skills"
ignore_file: "~/.agents/skills/ignore.json"
- path: "skills"
rules:
check-relative-paths:
severity: error
within-skill: true
individual_skills:
- path: "my_custom_standalone_skill"
rules:
check-trailing-whitespace: warning
ignore_file: "my_ignores.json"
Then you can simply run:
dart run skills_lint
A rule name that matches no built-in rule is a configuration error. Custom
rules can’t be configured in skills_lint.yaml.
Rule Precedence
When resolving which severity and parameters to apply for a rule, skills_lint evaluates settings in the following order of precedence (highest to lowest):
- CLI Flags / API Overrides:
- Rule severity overrides: Explicit flags passed to the CLI (e.g.,
--check-trailing-whitespaceor--no-check-trailing-whitespace). - Rule parameter overrides: Namespaced command-line parameter flags (e.g.,
--path-does-not-exist-exclude=".*-workspace"). Passing an empty string (e.g.--path-does-not-exist-exclude="") explicitly clears the custom parameter.
- Rule severity overrides: Explicit flags passed to the CLI (e.g.,
- Path-Specific Config: Rules defined under
directories:orindividual_skills:inskills_lint.yamlfor a matching path.- If a target config specifies a map (e.g.
path-does-not-exist: { severity: error, exclude: "..." }), the parameters map completely overrides any global parameters for that rule. - If a target config specifies a simple string severity (e.g.
path-does-not-exist: error), the severity is overridden, but the global parameters map is inherited/preserved.
- If a target config specifies a map (e.g.
- Global Config: Rules and parameters defined under the top-level
rules:inskills_lint.yaml. - Defaults: The hardcoded defaults for each rule.
This ensures that you can always override configuration file settings for a specific run by using CLI flags.
3. Custom Rules
Custom rule authoring lives in the
skills-lint-validation
skill — that skill walks through extending SkillRule and passing the
rule into the linter.
Built-in Rules
For the full list of built-in validation rules — default severities, exact
diagnostic shapes, auto-fix behavior, and configuration options — see
RULES.md.
Recipes
Drop-in snippets for the most common ways to wire skills_lint into a
project’s quality gates. Each recipe is exercised by
repo_test/recipe_drift_test.dart, so if a
flag here goes stale, CI fails.
Recipe: GitHub Actions
Save the following as .github/workflows/lint-skills.yml. It runs on
every push and PR, installs skills_lint globally on the runner,
and validates every skill under .claude/skills/. Adjust the path to
match where your skills live.
# .github/workflows/lint-skills.yml
name: Lint Agent Skills
on:
push:
branches: [main]
pull_request:
permissions: read-all
jobs:
lint-skills:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dart-lang/setup-dart@v1
- run: dart install skills_lint
- run: skills_lint --skills-directory ./.claude/skills
To validate a single skill directory instead, swap the last step:
- run: skills_lint --skill ./.claude/skills/my-skill
Recipe: GitHub Code Scanning (SARIF)
--format=sarif emits a SARIF 2.1.0 document that GitHub Code Scanning
accepts, which surfaces each lint as an annotation on the pull request
diff and as an entry in the repository’s Security tab. Save the
following as .github/workflows/code-scanning.yml.
# .github/workflows/code-scanning.yml
name: Skills Code Scanning
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
scan-skills:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dart-lang/setup-dart@v1
- run: dart install skills_lint
# The linter exits 1 when it finds violations. continue-on-error
# lets the upload step run first, so findings reach Code Scanning
# instead of disappearing with the failed job.
- name: Generate SARIF report
id: lint
continue-on-error: true
run: skills_lint --skills-directory ./.claude/skills --format=sarif > skills-lint.sarif
# Pull requests from forks cannot write security events, so skip
# the upload there rather than failing the job on a permission
# error.
- name: Upload SARIF report
if: ${{ !cancelled() && steps.lint.conclusion != 'skipped' && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }}
uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
with:
sarif_file: skills-lint.sarif
category: skills_lint
- name: Fail on lint violations
if: steps.lint.outcome != 'success'
run: exit 1
Notes:
- The report goes to standard output, so redirect it to a file.
Operational failures go to standard error and never mix into the report.
Bugs in skills_lint start with
skills_lint internal error:, and problems in your files start withError:. - Result paths are recorded relative to the repository root using the
SARIF
%SRCROOT%base, so annotations land on the right lines even when the linter runs from a subdirectory. - Code Scanning is available on public repositories and on private
repositories with GitHub Advanced Security. Without it, the upload
step fails; drop it and keep
--format=sarifoutput as a build artifact instead. - Use
--format=jsonfor a plain array of results when feeding a consumer other than Code Scanning.
Recipe: Dart-native pre-commit hook
A pre-commit hook that calls into the linter directly — no Husky, no
Python pre-commit framework, just Dart and the existing
dart install tooling.
Install the linter globally once per machine:
dart install skills_lint
Then install the hook into the repository (run from the repo root):
cat > .git/hooks/pre-commit <<'HOOK'
#!/bin/sh
set -e
# Lint every skill under .claude/skills before each commit.
# Add --skill arguments for other locations as needed.
exec skills_lint --skills-directory ./.claude/skills --quiet
HOOK
chmod +x .git/hooks/pre-commit
The hook exits non-zero on lint failure, blocking the commit. To
auto-apply fixable lints inside the hook, append --fix to the linter
invocation.
Recipe: have an agent set it up for you
If you use an agent that can read repository-local skills, it can
set up skills_lint for you with the
skills-lint-setup and
skills-lint-validation
skills.
-
Install the skills into
.agents/skills/. This step needs a Dart package. From its root:dart pub add dev:skills_lint dart run skills@^1.0.0 get --agent generic --package skills_lint --all -
Paste this prompt into your agent:
Set up skills_lint in this project. Use the skill at
.agents/skills/skills-lint-setup/SKILL.mdto create the configuration file and the test. Then use the skill at.agents/skills/skills-lint-validation/SKILL.mdto run the linter and resolve any failures.
To run the linter in CI, see the other Recipes.
Contributing
Contributions are welcome! Please ensure that any PRs pass the linter and test suite.
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add google/skills_lint.dart