In any language, fail the PR when your AGENTS.md, llms.txt, or CLAUDE.md points at a command, script, or path that no longer exists.
reflint is a reference-integrity linter for agent config files. It doesn't grade style or prose — it checks whether the references are real: the scripts, paths, and files your config tells an AI agent to use. Zero-dependency, language-agnostic, runs in CI on every PR.
AGENTS.md や llms.txt の中の「もう解決できない参照」を、どのスタックでも毎PRで落とす。
AIエージェント向け設定ファイルの 参照整合性 (reference integrity) を CI で検証するリンタ。表記や文体ではなく、AIに渡す "嘘の指示" そのもの ── 存在しないコマンド・スクリプト・パス ── を落とす。依存ゼロ・言語非依存。
Agent config rots silently. When it tells the agent to run a script that was renamed, or points at a path that was deleted, the agent trusts it and breaks things. reflint fails CI before that happens. It checks facts, not prose — so it works in any stack (Go, Rust, Python, Ruby, JS…), and it treats llms.txt as a first-class target, which the rest of the ecosystem's format validators don't check for real references.
# .github/workflows/reflint.yml
name: reflint
on: [push, pull_request]
jobs:
reflint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 } # needed for the "new in this PR" diff
- uses: hyuga611/reflint@v1 # auto-detects AGENTS.md / llms.txt / CLAUDE.mdFindings show up as inline PR annotations, and the job fails (exit 1) so a stale config can't be merged.
A document that has been edited for a year usually has some stale references already. If the first
run turns the PR red, the linter gets removed — so on pull_request reflint fails only on
references this PR broke, and reports the rest as a count:
reflint: 1 new since origin/main broken reference (12 pre-existing, not failing this run — run without --since to see them)
"Broke" is judged against the base commit's tree, so it catches both editing a document to point at something that isn't there and deleting a file that a document still points at.
- uses: hyuga611/reflint@v1
with:
since: '' # default: the PR base on pull_request
# since: off # check every reference, every time (the old behaviour)npx @hyuga/reflint # AGENTS.md / llms.txt / CLAUDE.md を自動検出
npx @hyuga/reflint docs/AGENTS.md llms.txt
npx @hyuga/reflint --code-blocks # ```コードブロック``` 内の拡張子付きパスも検査(opt-in)
npx @hyuga/reflint --format json # 機械可読な JSON 出力(エディタ拡張・他ツール連携の下地)
npx @hyuga/reflint --ignore llms-full.txt,docs/legacy.md # 個別に無視(カンマ区切り・REFLINT_IGNORE でも可)
npx @hyuga/reflint --since origin/main # このPRで新しく壊れた参照だけで落とす(既存の債務は件数だけ報告・REFLINT_SINCE でも可)
# npm i -g @hyuga/reflint すると `reflint` コマンドで使えます
# monorepo では、対象ファイルに最も近い package.json の scripts を自動で参照しますWhat it catches:
- Markdown link targets (
[text](path)) inllms.txt/AGENTS.mdthat point at repo files which don't exist — the llms.txt referential-integrity check nobody else does in CI - Back-quoted paths/files that don't exist on disk — language-agnostic (works in any repo)
npm run <script>/pnpm <script>etc. that isn't inpackage.json(suggests the nearest name) — for JS repos- Exit code 1 when anything is wrong = a CI gate
What it deliberately doesn't flag — a linter is abandoned after one false positive, so precision wins over recall:
- Bare format names in prose (
AGENTS.md,llms.txt,CLAUDE.md,SKILL.md,README.md,.cursorrules…). Writing about the format isn't a reference. Qualify it with a directory (docs/llms.txt) and it's checked again - Absolute paths, drive letters, globs,
<placeholders>, URLs, and slash-commands - Anything you pass to
--ignore
Already run textlint over your docs? reflint ships a textlint-compatible rule at @hyuga/reflint/textlint-rule, so you can fold the referential-integrity check into your existing textlint pass instead of adding a separate CI step.
import { TextlintKernel } from "@textlint/kernel";
import markdown from "@textlint/textlint-plugin-markdown";
import reflint from "@hyuga/reflint/textlint-rule";
const kernel = new TextlintKernel();
await kernel.lintText(text, {
ext: ".md",
plugins: [{ pluginId: "markdown", plugin: markdown }],
rules: [{ ruleId: "reflint", rule: reflint, options: { codeBlocks: false } }],
});markdown-link-check checks whether external links are alive; reflint checks whether repo-relative references resolve. They're complementary — run both.
- Reference-integrity core: file paths (any language) + npm scripts (zero-dep) —
src/check.mjs - GitHub Action (
action.yml) + inline PR annotations + self-CI -
llms.txtmarkdown-link referential integrity — repo-relative link targets must resolve (the wedge no one else covers in CI) - Paths inside fenced code blocks — opt-in
--code-blocks(extension-bearing, repo-relative paths only, to stay false-positive-free) - textlint rule adapter (
@hyuga/reflint/textlint-rule, experimental) — reuse reflint inside an existing textlint pass -
--format jsonmachine-readable output + monorepo resolution (nearestpackage.json, path found from the file's dir or repo root) - Precision pass — prose mentions of format names no longer flagged, plus
--ignore/REFLINT_IGNOREas an escape hatch
node --test # unit tests
npm run poc # サンプル(意図的に不整合)で検出デモ → exit 1
node src/check.mjs # このリポジトリ自身の AGENTS.md を検査 → exit 0Zero-dependency CI linters for repos where AI agents do the work. Each one fails the PR on something that breaks quietly.
| Catches | |
|---|---|
| reflint ← you are here | AGENTS.md / llms.txt / CLAUDE.md pointing at commands, scripts, or paths that no longer exist |
| skills-lint | SKILL.md broken references + name/trigger collisions between skills |
| carrylint | Skills with the author's machine or model baked in — absolute paths, undeclared CLIs, unresolved placeholders |
| genchi | Agents reporting "done" without re-fetching real-world state |
| tracklint | Forms and CTAs that quietly stopped being wired for conversion tracking |
| tokenlint | Hardcoded colors that bypass your design tokens |
| reflint for VS Code | The same reflint checks, inline in the editor as you save |
| orogami | Not a linter — natural Japanese/CJK line breaking for OGP images (BudouX + font subsetting) |
MIT