A harness linter, written in Rust.
Install · Documentation · Built-in rules · Releases
Harness engineering is hard. Keeping a team aligned on it is harder. Your coding agents depend on instructions, skills, and Markdown docs that stay consistent as projects change. Ryni catches broken local links and invalid Agent Skills metadata before they get in the way.
- Check your harness. Built-in checks for Markdown links and Agent Skills.
- Run anywhere. Written in Rust, distributed as a standalone binary.
- Start immediately. Run
ryni check .. No configuration required.
Share team conventions: versioned local TOML packs select rules and configure preview checks for metadata, document sections, required files and duplicate skill names. See configuration and packs.
macOS and Linux:
curl -LsSf https://github.com/computerlovetech/ryni/releases/latest/download/ryni-installer.sh | shWindows PowerShell:
powershell -ExecutionPolicy Bypass -c "irm https://github.com/computerlovetech/ryni/releases/latest/download/ryni-installer.ps1 | iex"No Rust installation is needed. Follow the installer's PATH instructions, then
run ryni --version. Rerun the installer to update. Prebuilt archives are also
available on GitHub Releases.
For development, install from this repository with Rust and Cargo:
cargo install --path . --lockedSee RELEASE.md for version pinning, custom install locations, and the release process.
ryni check . # Current directory
ryni check ./my-project # A project directory
ryni check ./skills/review # A single skill directoryNo configuration is needed. Ryni recursively checks .md and .markdown files
(case-insensitive extensions) for broken local links. Files named exactly
SKILL.md also receive all five Agent Skills metadata checks. Hidden directories such as .agents/skills are
included unless ignored. Nested symlinks are skipped.
Ryni respects .gitignore, .ignore, .git/info/exclude, and global Git
excludes, including applicable parent and nested ignore files. Git ignore rules
apply inside Git repositories; .ignore also works outside them. Matching files
are skipped even if tracked by Git. Global excludes can make scan coverage differ
between your machine and CI.
ryni check . --no-ignore # Disable ignore-file filtering
ryni check . --exclude 'vendor/**' --exclude 'third_party/**'--exclude accepts repeatable gitignore-style globs relative to the scan root
and still applies with --no-ignore. Patterns containing a slash are relative
to that root; patterns such as *.md match at any depth. Quote globs to prevent
shell expansion. Filtering only controls which files are scanned: links to
existing targets inside excluded directories remain valid.
Example output:
markdown-local-link: Target "docs/testing.md" does not exist
--> README.md:3:1
|
3 | [Testing](docs/testing.md)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
Found 1 error.
Diagnostics show the rule, file location, and source context. Broken links are
underlined; skill metadata findings show a preview of the file without guessing
an individual field location. Terminal output uses color; redirected output is
plain text. Set NO_COLOR to disable color.
A clean scan prints All checks passed!. When no supported files are found,
ryni prints No supported files found. and exits successfully.
Exit codes: 0 passed or no supported files, 1 violations, 2 execution error.
Use the same command in CI. Files change only when --fix is explicitly enabled.
| Rule | Status | Check |
|---|---|---|
skill-frontmatter |
stable | SKILL.md must start with delimited YAML containing a mapping with unique string keys. |
skill-name |
stable | The name must contain 1–64 lowercase Unicode letters, numbers or hyphens, without leading, trailing or consecutive hyphens. |
skill-directory-name |
stable | The skill name must exactly match its containing directory. |
skill-description |
stable | The description must be a nonblank string of at most 1,024 Unicode characters. |
skill-optional-fields |
stable | Validate license, allowed-tools, compatibility and metadata against the Agent Skills field requirements. |
markdown-local-link |
stable | Relative Markdown links and images must point to existing files or directories. URL schemes and document anchors are skipped. |
Malformed frontmatter produces one skill-frontmatter finding. Dependent checks
are skipped for that file. Character limits count Unicode
characters, not UTF-8 bytes. Names are not trimmed or Unicode-normalized.
These checks cover the requirements above, not full semantic compliance. They do not judge description quality, validate instruction bodies, require optional directories, or reject additional frontmatter fields.
Links resolve relative to the Markdown file containing them, including ../
paths. Images and reference-style links are checked too. Diagnostics point to the
link's start (or its reference use), with one-based line and character column:
markdown-local-link: Target "docs/testing.md" does not exist
--> README.md:12:1
[Guide](guide.md#installation) checks that guide.md exists; it does not check
whether the heading exists. Queries are also removed before checking. URL-encoded
paths such as my%20guide.md are decoded. Existing directories and links to files
outside the scanned directory are accepted. Linked symlinks are resolved.
The rule skips URL schemes (including web and email links), root-relative links
such as /docs/guide, network URLs, and same-document anchors. Code examples,
frontmatter, raw HTML attributes, and plain-text paths are not checked. Undefined
Markdown reference labels are not filesystem targets and are not checked.
Paths are checked literally: no automatic .md extension, website routing, or
build-template expansion. Generated targets must already exist when checking;
intentional broken links are reported too. Invalid filenames and paths that are
too long produce findings without stopping the scan. Other inspection failures,
such as permission errors, remain execution errors. There are no suppressions yet.
ryni check README.md --select markdown-local-link
ryni check . --output-format json
ryni check . --exclude vendor --respect-ignore
ryni settings .
ryni rule skill-directory-name
ryni check . --fix --unsafe-fixesThe six stable rules remain the default. Optional ryni.toml configuration and
versioned local packs can select rules and add preview conventions. JSON results
include structured findings, source positions, proposed fixes and execution errors.
Incomplete scans retain available findings and exit with code 2.
The directory/name fix is unsafe: it changes skill identity and rewrites YAML formatting and comments. It requires explicit opt-in. See configuration, output and fixes, and the architecture guide.
cargo run -- check .
cargo test --locked
cargo clippy --all-targets --locked -- -D warnings
cargo fmt --checkChecking this repository discovers the valid skill fixture in
tests/fixtures/skills. Rule implementations live under src/rules/.
The opt-in repository benchmark scans 15 pinned open-source projects across 15 languages, records timings and diagnostics, and compares behavior against a reviewed baseline.
The MkDocs source lives in website/, with MkDocs Material and the shared
Computerlove styling used by Umbod. Use uv to preview or build it:
uv run --locked --only-group docs mkdocs serve
uv run --locked --only-group docs mkdocs build --strictPython is used for development tooling; the CLI is written in Rust.
Documentation pull requests are built in CI. Changes merged to main deploy to
GitHub Pages.